wp-pay-gateways/adyen

View on GitHub
src/SiteHealthController.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php
/**
 * Site Health controller
 *
 * @author    Pronamic <info@pronamic.eu>
 * @copyright 2005-2022 Pronamic
 * @license   GPL-3.0-or-later
 * @package   Pronamic\WordPress\Pay\Gateways\Adyen
 */

namespace Pronamic\WordPress\Pay\Gateways\Adyen;

use WP_Error;
use WP_REST_Request;

/**
 * Site Health controller
 *
 * @author  Remco Tolsma
 * @version 1.0.5
 * @since   1.0.5
 */
class SiteHealthController {
    /**
     * HTTP Basic authentication test username.
     *
     * @var string
     */
    const HTTP_BASIC_AUTHENTICATION_TEST_USERNAME = 'test';

    /**
     * HTTP Basic authentication test password.
     *
     * @var string
     */
    const HTTP_BASIC_AUTHENTICATION_TEST_PASSWORD = '1234';

    /**
     * Setup.
     *
     * @return void
     */
    public function setup() {
        add_filter( 'site_status_tests', array( $this, 'site_status_tests' ) );

        $prefix = 'health-check-';
        $action = 'pronamic-pay-adyen-http-authorization-test';

        add_action( 'wp_ajax_' . $prefix . $action, array( $this, 'wp_ajax_health_check_http_authorization_test' ) );

        add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
    }

    /**
     * Site status tests.
     *
     * @link https://make.wordpress.org/core/2019/04/25/site-health-check-in-5-2/
     * @param array<string, array<string, array<string, string>>> $tests Tests.
     * @return array<string, array<string, array<string, string>>>
     */
    public function site_status_tests( $tests ) {
        $tests['async']['pronamic_pay_adyen_http_authorization_test'] = array(
            'label' => __( 'HTTP Authorization header test', 'pronamic_ideal' ),
            'test'  => 'pronamic-pay-adyen-http-authorization-test',
        );

        return $tests;
    }

    /**
     * Get HTTP authorization header.
     *
     * @return string
     */
    private function get_http_authorization_header() {
        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Benign reason.
        return 'Basic ' . base64_encode( self::HTTP_BASIC_AUTHENTICATION_TEST_USERNAME . ':' . self::HTTP_BASIC_AUTHENTICATION_TEST_PASSWORD );
    }

    /**
     * Get HTTP authorization test.
     *
     * @return array<string, string|array<string, string>>
     */
    private function get_http_authorization_test() {
        $result = array(
            'label'       => __( 'HTTP Basic authentication is working', 'pronamic_ideal' ),
            'status'      => 'good',
            'badge'       => array(
                'label' => __( 'Payments', 'pronamic_ideal' ),
                'color' => 'blue',
            ),
            'description' => sprintf(
                '<p>%s</p>',
                __( 'HTTP Basic authentication is required to securely receive Adyen notifications.', 'pronamic_ideal' )
            ),
            'actions'     => '',
            'test'        => 'pronamic-pay-adyen-http-authorization-test',
        );

        $rest_url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/http-authorization-test' );

        $request_authorization = $this->get_http_authorization_header();

        $response = wp_remote_get(
            $rest_url,
            array(
                'headers' => array(
                    'Authorization' => $request_authorization,
                ),
            )
        );

        if ( $response instanceof \WP_Error ) {
            $result['status'] = 'critical';

            $result['label'] = __( 'Could not reach HTTP Authorization header test endpoint.', 'pronamic_ideal' );

            $result['description'] .= sprintf(
                '<p>%s</p>',
                sprintf(
                    '<span class="error"><span class="screen-reader-text">%s</span></span> %s',
                    __( 'Error', 'pronamic_ideal' ),
                    sprintf(
                        /* translators: %s: The error returned by the lookup. */
                        __( 'Your site is unable to test the HTTP Authorization header, and returned the error: %s' ),
                        $response->get_error_message()
                    )
                )
            );

            return $result;
        }

        // Body.
        $body = \wp_remote_retrieve_body( $response );

        // Response.
        $response_code    = \wp_remote_retrieve_response_code( $response );
        $response_message = \wp_remote_retrieve_response_message( $response );

        // Data.
        $data = json_decode( $body );

        // JSON error.
        $json_error = json_last_error();

        if ( \JSON_ERROR_NONE !== $json_error ) {
            $result['status'] = 'critical';

            $result['description'] .= \sprintf(
                'Could not JSON decode response, HTTP response: "%s %s", HTTP body length: "%d", JSON error: "%s".',
                $response_code,
                $response_message,
                \strlen( $body ),
                \json_last_error_msg()
            );

            return $result;
        }

        // Object.
        if ( ! \is_object( $data ) ) {
            $result['status'] = 'critical';

            $result['description'] .= \sprintf(
                'Could not JSON decode response to an object, HTTP response: "%s %s", HTTP body: "%s".',
                $response_code,
                $response_message,
                $body
            );

            return $result;
        }

        if ( ! property_exists( $data, 'authorization' ) ) {
            $result['status'] = 'critical';

            return $result;
        }

        if ( $data->authorization !== $request_authorization ) {
            $result['status'] = 'critical';

            return $result;
        }

        $result['status'] = 'good';

        return $result;
    }

    /**
     * WordPress AJAX health check HTTP authorization test.
     *
     * @link https://github.com/WordPress/WordPress/blob/5.2.4/wp-admin/includes/class-wp-site-health.php#L1128-L1189
     * @link https://github.com/WordPress/WordPress/blob/5.2.4/wp-admin/includes/ajax-actions.php#L4865-L4883
     * @return void
     */
    public function wp_ajax_health_check_http_authorization_test() {
        check_ajax_referer( 'health-check-site-status' );

        if ( ! current_user_can( 'view_site_health_checks' ) ) {
            wp_send_json_error();
        }

        $result = $this->get_http_authorization_test();

        wp_send_json_success( $result );
    }

    /**
     * REST API init.
     *
     * @link https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/
     * @link https://developer.wordpress.org/reference/hooks/rest_api_init/
     * @return void
     */
    public function rest_api_init() {
        register_rest_route(
            Integration::REST_ROUTE_NAMESPACE,
            '/http-authorization-test',
            array(
                'methods'             => 'GET',
                'callback'            => array( $this, 'rest_api_http_authorization_test' ),
                'permission_callback' => '__return_true',
            )
        );
    }

    /**
     * REST API Adyen notifications handler.
     *
     * @link https://www.php.net/manual/en/features.http-auth.php
     * @param WP_REST_Request $request Request.
     * @return object
     */
    public function rest_api_http_authorization_test( WP_REST_Request $request ) {
        $data = array(
            'authorization' => $request->get_header( 'Authorization' ),
        );

        $server_keys = array(
            'HTTP_AUTHORIZATION',
            'PHP_AUTH_USER',
            'PHP_AUTH_PW',
            'AUTH_TYPE',
        );

        foreach ( $server_keys as $key ) {
            if ( array_key_exists( $key, $_SERVER ) ) {
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
                $data[ $key ] = wp_unslash( $_SERVER[ $key ] );
            }
        }

        return (object) $data;
    }
}