/home/sylamedg/www/wp-content/plugins/templately/includes/Core/Importer/Utils/SignatureVerifier.php
<?php
namespace Templately\Core\Importer\Utils;

use Templately\Utils\Helper;

/**
 * Class SignatureVerifier
 *
 * Verifies HMAC-SHA256 signatures from Templately backend callbacks
 * to prevent arbitrary file write vulnerabilities.
 *
 * @package Templately\Core\Importer\Utils
 */
class SignatureVerifier {

    /**
     * Verify callback signature from Templately backend.
     *
     * @param array  $payload   Request payload data
     * @param string $signature Signature from X-Templately-Signature header
     * @param int    $timestamp Timestamp from X-Templately-Timestamp header
     * @param string $api_key   User's API key (used as secret)
     * @param int    $tolerance Time tolerance in seconds (default: 300 = 5 minutes)
     * @return bool|WP_Error True if valid, WP_Error otherwise
     */
    public static function verify($payload, $signature, $timestamp, $api_key, $tolerance = 300) {
        if (empty($api_key)) {
            return Helper::error(
                'missing_api_key',
                __('API key not provided for signature verification', 'templately'),
                'verify_signature',
                401
            );
        }

        // Validate inputs
        if (empty($signature) || empty($timestamp) || !is_numeric($timestamp)) {
            return Helper::error(
                'invalid_signature_headers',
                __('Invalid signature or timestamp headers', 'templately'),
                'verify_signature',
                401
            );
        }

        // Check timestamp to prevent replay attacks
        if (!self::is_timestamp_valid((int) $timestamp, $tolerance)) {
            return Helper::error(
                'timestamp_expired',
                __('Callback timestamp expired or invalid', 'templately'),
                'verify_signature',
                401
            );
        }

        // Generate expected signature using API key
        $expected_signature = self::generate_signature($payload, (int) $timestamp, $api_key);

        // Use hash_equals to prevent timing attacks
        if (!hash_equals($expected_signature, $signature)) {
            return Helper::error(
                'invalid_signature',
                __('Invalid signature', 'templately'),
                'verify_signature',
                401
            );
        }

        return true;
    }

    /**
     * Check if timestamp is within acceptable tolerance.
     *
     * @param int $timestamp Unix timestamp to check
     * @param int $tolerance Tolerance in seconds
     * @return bool True if timestamp is valid
     */
    private static function is_timestamp_valid($timestamp, $tolerance) {
        $current_time = time();
        $time_difference = abs($current_time - $timestamp);

        return $time_difference <= $tolerance;
    }

    /**
     * Generate HMAC-SHA256 signature for payload.
     *
     * @param array  $payload   Request payload
     * @param int    $timestamp Unix timestamp
     * @param string $api_key   User's API key (used as secret)
     * @return string HMAC signature
     */
    private static function generate_signature($payload, $timestamp, $api_key) {
        $canonical_string = self::create_canonical_string($payload, $timestamp);
        return hash_hmac('sha256', $canonical_string, $api_key);
    }

    /**
     * Create canonical string from payload and timestamp.
     *
     * Only includes security-critical fields in signature to avoid
     * performance issues with large template content.
     *
     * @param array $payload Request payload
     * @param int   $timestamp Unix timestamp
     * @return string Canonical string
     */
    private static function create_canonical_string($payload, $timestamp) {
        // Extract only security-critical fields for signature
        // Exclude large content fields like 'template' and 'error'
        // Also exclude 'isSkipped' as requested
        $signature_fields = [
            'process_id'  => isset($payload['process_id']) ? $payload['process_id'] : null,
            'content_id'  => isset($payload['content_id']) ? $payload['content_id'] : null,
            'template_id' => isset($payload['template_id']) ? $payload['template_id'] : null,
            'type'        => isset($payload['type']) ? $payload['type'] : null,
        ];

        // Remove null values
        $signature_fields = array_filter($signature_fields, function ($value) {
            return $value !== null;
        });

        // Sort keys for consistency
        ksort($signature_fields);

        // JSON encode with consistent flags
        $payload_json = wp_json_encode($signature_fields, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        // Combine timestamp and payload
        return $timestamp . '.' . $payload_json;
    }
}