Skip to content

Commit

Permalink
SP-965 Validating incoming webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kwiatkowski committed Sep 12, 2024
1 parent 668dd75 commit 1ea7dec
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 75 deletions.
1 change: 0 additions & 1 deletion .phpunit.result.cache

This file was deleted.

26 changes: 26 additions & 0 deletions BitPayLib/class-bitpaycreateorder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace BitPayLib;

class BitPayCreateOrder {

public const BITPAY_TOKEN_ORDER_METADATA_KEY = '_bitpay_token';

private BitPayPaymentSettings $bitpay_payment_settings;

public function __construct(
BitPayPaymentSettings $bitpay_payment_settings,
) {
$this->bitpay_payment_settings = $bitpay_payment_settings;
}

public function execute( int $order_id ): void {
$token = $this->bitpay_payment_settings->get_bitpay_token();
$order = new \WC_Order( $order_id );

$order->update_meta_data( self::BITPAY_TOKEN_ORDER_METADATA_KEY, $token );
$order->save_meta_data();
}
}
37 changes: 24 additions & 13 deletions BitPayLib/class-bitpayipnprocess.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ class BitPayIpnProcess {
private BitPayLogger $logger;
private BitPayClientFactory $factory;
private BitPayWordpressHelper $bitpay_wordpress_helper;
private BitPayWebhookVerifier $bitpay_webhook_verifier;
private BitPayPaymentSettings $bitpay_payment_settings;
private BitPayWebhookVerifier $bitpay_webhook_verifier;
private BitPayPaymentSettings $bitpay_payment_settings;

public function __construct(
BitPayCheckoutTransactions $bitpay_checkout_transactions,
BitPayClientFactory $factory,
BitPayWordpressHelper $bitpay_wordpress_helper,
BitPayLogger $logger,
BitPayWebhookVerifier $bitpay_webhook_verifier,
BitPayPaymentSettings $bitpay_payment_settings
BitPayWebhookVerifier $bitpay_webhook_verifier,
BitPayPaymentSettings $bitpay_payment_settings,
) {
$this->bitpay_checkout_transactions = $bitpay_checkout_transactions;
$this->logger = $logger;
$this->factory = $factory;
$this->bitpay_wordpress_helper = $bitpay_wordpress_helper;
$this->bitpay_webhook_verifier = $bitpay_webhook_verifier;
$this->bitpay_payment_settings = $bitpay_payment_settings;
$this->bitpay_webhook_verifier = $bitpay_webhook_verifier;
$this->bitpay_payment_settings = $bitpay_payment_settings;
}

public function execute( WP_REST_Request $request ): void {
Expand All @@ -51,16 +51,11 @@ public function execute( WP_REST_Request $request ): void {
$data['event'] = $event;
$data['requestDate'] = date( 'Y-m-d H:i:s' );
$invoice_id = $data['id'] ?? null;
$x_signature = $request->get_header( 'x-signature' );

$this->logger->execute( $data, 'INCOMING IPN', true );

$is_webhook_verified = $this->bitpay_webhook_verifier->verify(
$this->bitpay_payment_settings->get_bitpay_token(), // token used to create resource
$request->get_header('x-signature'),
$request->get_body()
);

if ( ! $event || ! $data || ! $invoice_id || ! $is_webhook_verified ) {
if ( ! $event || ! $data || ! $invoice_id || ! $x_signature ) {
$this->logger->execute( 'Wrong IPN request', 'INCOMING IPN ERROR', false, true );
return;
}
Expand All @@ -70,6 +65,8 @@ public function execute( WP_REST_Request $request ): void {
do_action( 'bitpay_checkout_woocoomerce_after_get_invoice', $bitpay_invoice );
$order = $this->bitpay_wordpress_helper->get_order( $bitpay_invoice->getOrderId() );
$this->validate_order( $order, $invoice_id );
$this->validate_webhook( $x_signature, $request->get_body(), $order );

$this->process( $bitpay_invoice, $order, $event['name'] );
} catch ( BitPayInvalidOrder $e ) { // phpcs:ignore
// do nothing.
Expand Down Expand Up @@ -331,4 +328,18 @@ private function should_process_refund(): bool {
$should_process_refund_status = $this->get_wc_order_statuses()['bitpay_checkout_order_process_refund'] ?? '1';
return '1' === $should_process_refund_status;
}

private function validate_webhook( string $x_signature, string $webhook_body, WC_Order $order ): void {
$order_bitpay_token = $order->get_meta( BitPayCreateOrder::BITPAY_TOKEN_ORDER_METADATA_KEY );

if ( $order_bitpay_token &&
! $this->bitpay_webhook_verifier->verify(
$this->bitpay_payment_settings->get_bitpay_token(),
$x_signature,
$webhook_body
)
) {
throw new \Exception( 'IPN Request failed HMAC validation' );
}
}
}
12 changes: 11 additions & 1 deletion BitPayLib/class-bitpaypluginsetup.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ class BitPayPluginSetup {
private BitPayPaymentSettings $bitpay_payment_settings;
private BitPayInvoiceCreate $bitpay_invoice_create;
private BitPayCheckoutTransactions $bitpay_checkout_transactions;
private BitPayCreateOrder $bitpay_create_order;

public function __construct() {
$this->bitpay_payment_settings = new BitPayPaymentSettings();
$factory = new BitPayClientFactory( $this->bitpay_payment_settings );
$cart = new BitPayCart();
$logger = new BitPayLogger();
$wordpress_helper = new BitPayWordpressHelper();
$webhook_verifier = new BitPayWebhookVerifier();
$webhook_verifier = new BitPayWebhookVerifier();
$this->bitpay_checkout_transactions = new BitPayCheckoutTransactions( $wordpress_helper );
$this->bitpay_ipn_process = new BitPayIpnProcess( $this->bitpay_checkout_transactions, $factory, $wordpress_helper, $logger, $webhook_verifier, $this->bitpay_payment_settings );
$this->bitpay_cancel_order = new BitPayCancelOrder( $cart, $this->bitpay_checkout_transactions, $logger );
Expand All @@ -43,6 +44,9 @@ public function __construct() {
$wordpress_helper,
$logger
);
$this->bitpay_create_order = new BitPayCreateOrder(
$this->bitpay_payment_settings
);
}

public function execute(): void {
Expand All @@ -59,6 +63,8 @@ public function execute(): void {
add_filter( 'woocommerce_payment_gateways', array( $this, 'wc_bitpay_checkout_add_to_gateways' ) );
add_filter( 'woocommerce_order_button_html', array( $this, 'bitpay_checkout_replace_order_button_html' ), 10, 2 );
add_action( 'woocommerce_blocks_loaded', array( $this, 'register_payment_block' ) );
add_action( 'woocommerce_new_order', array( $this, 'bitpay_create_order' ) );
add_action( 'woocommerce_update_order', array( $this, 'bitpay_create_order' ) );

// http://<host>/wp-json/bitpay/ipn/status url.
// http://<host>/wp-json/bitpay/cartfix/restore url.
Expand Down Expand Up @@ -222,4 +228,8 @@ function () {
5
);
}

public function bitpay_create_order( int $order_id ): void {
$this->bitpay_create_order->execute( $order_id );
}
}
21 changes: 12 additions & 9 deletions BitPayLib/class-bitpaywebhookverifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
namespace BitPayLib;

class BitPayWebhookVerifier {
public function verify($signing_key, $sig_header, $webhook_body): bool {
$hmac = base64_encode( hash_hmac(
'sha256',
$webhook_body,
$signing_key,
true
) );
public function verify( string $signing_key, string $sig_header, string $webhook_body ): bool {
// phpcs:ignore
$hmac = base64_encode(
hash_hmac(
'sha256',
$webhook_body,
$signing_key,
true
)
);

return $sig_header !== $hmac;
}
return $sig_header === $hmac;
}
}
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
],
"post-update-cmd": [
"composer add-prefix"
],
"phpcbf": [
"./vendor/bin/phpcbf BitPayLib"
]
},
"config": {
Expand Down
3 changes: 2 additions & 1 deletion tests/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env
.env
.phpunit.result.cache
Loading

0 comments on commit 1ea7dec

Please sign in to comment.