Version 1.3.1 - Switch to SecureLicenseClient with signature verification

- Upgraded from LicenseClient to SecureLicenseClient with HMAC-SHA256 response signature verification
- Added Server Secret configuration field for secure communication
- Added rate limit exception handling with retry time display
- Added signature verification error handling
- Added URL validation error handling (SSRF protection)
- Updated all translation files with new strings
- Compiled .mo files for all 7 language variants

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 19:23:42 +01:00
parent cbe758267e
commit 7286459ff2
19 changed files with 317 additions and 51 deletions

View File

@@ -154,6 +154,16 @@ if (!class_exists('WC_TPP_Settings')) {
'desc_tip' => true,
),
array(
'title' => __('Server Secret', 'wc-tier-package-prices'),
'desc' => __('The shared secret for secure communication with the license server.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_license_server_secret',
'type' => 'password',
'default' => '',
'css' => 'min-width:400px;',
'desc_tip' => true,
),
array(
'title' => __('License Status', 'wc-tier-package-prices'),
'type' => 'wc_tpp_license_status',
@@ -188,13 +198,14 @@ if (!class_exists('WC_TPP_Settings')) {
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
if (empty($license_key) || empty($server_url)) {
wp_send_json_error(array('message' => __('License key and server URL are required.', 'wc-tier-package-prices')));
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
}
try {
$client = $this->get_license_client($server_url);
$client = $this->get_license_client($server_url, $server_secret);
$domain = $this->get_current_domain();
$result = $client->validate($license_key, $domain);
@@ -212,16 +223,37 @@ if (!class_exists('WC_TPP_Settings')) {
'status' => $this->get_cached_license_status(),
));
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
wp_send_json_error(array(
'message' => sprintf(
/* translators: %d: Number of seconds to wait */
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
$e->retryAfter ?? 60
),
'code' => 'rate_limit_exceeded',
'retry_after' => $e->retryAfter,
));
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
delete_transient('wc_tpp_license_status');
wp_send_json_error(array(
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
'code' => 'signature_error',
));
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
delete_transient('wc_tpp_license_status');
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => $e->errorCode ?? 'unknown',
));
} catch (\InvalidArgumentException $e) {
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => 'invalid_url',
));
} catch (\Exception $e) {
delete_transient('wc_tpp_license_status');
wp_send_json_error(array(
'message' => $e->getMessage(),
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
'code' => 'exception',
));
}
@@ -239,13 +271,14 @@ if (!class_exists('WC_TPP_Settings')) {
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
if (empty($license_key) || empty($server_url)) {
wp_send_json_error(array('message' => __('License key and server URL are required.', 'wc-tier-package-prices')));
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
}
try {
$client = $this->get_license_client($server_url);
$client = $this->get_license_client($server_url, $server_secret);
$domain = $this->get_current_domain();
$result = $client->activate($license_key, $domain);
@@ -269,14 +302,34 @@ if (!class_exists('WC_TPP_Settings')) {
wp_send_json_error(array('message' => $result->message));
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
wp_send_json_error(array(
'message' => sprintf(
/* translators: %d: Number of seconds to wait */
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
$e->retryAfter ?? 60
),
'code' => 'rate_limit_exceeded',
'retry_after' => $e->retryAfter,
));
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
wp_send_json_error(array(
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
'code' => 'signature_error',
));
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => $e->errorCode ?? 'unknown',
));
} catch (\Exception $e) {
} catch (\InvalidArgumentException $e) {
wp_send_json_error(array(
'message' => $e->getMessage(),
'code' => 'invalid_url',
));
} catch (\Exception $e) {
wp_send_json_error(array(
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
'code' => 'exception',
));
}
@@ -285,14 +338,18 @@ if (!class_exists('WC_TPP_Settings')) {
/**
* Get license client instance
*
* @param string $server_url License server URL.
* Uses SecureLicenseClient for HMAC signature verification.
*
* @param string $server_url License server URL.
* @param string $server_secret Shared secret for signature verification.
* @return \Magdev\WcLicensedProductClient\LicenseClientInterface
*/
private function get_license_client(string $server_url): \Magdev\WcLicensedProductClient\LicenseClientInterface {
private function get_license_client(string $server_url, string $server_secret): \Magdev\WcLicensedProductClient\LicenseClientInterface {
$httpClient = \Symfony\Component\HttpClient\HttpClient::create();
return new \Magdev\WcLicensedProductClient\LicenseClient(
return new \Magdev\WcLicensedProductClient\SecureLicenseClient(
httpClient: $httpClient,
baseUrl: $server_url,
serverSecret: $server_secret,
);
}
@@ -402,6 +459,7 @@ if (!class_exists('WC_TPP_Settings')) {
return {
license_key: $('#wc_tpp_license_key').val(),
server_url: $('#wc_tpp_license_server_url').val(),
server_secret: $('#wc_tpp_license_server_secret').val(),
nonce: '<?php echo esc_js($nonce); ?>'
};
}
@@ -420,8 +478,8 @@ if (!class_exists('WC_TPP_Settings')) {
$validateBtn.on('click', function() {
var data = getLicenseData();
if (!data.license_key || !data.server_url) {
alert('<?php echo esc_js(__('Please enter both license server URL and license key.', 'wc-tier-package-prices')); ?>');
if (!data.license_key || !data.server_url || !data.server_secret) {
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
return;
}
@@ -443,8 +501,8 @@ if (!class_exists('WC_TPP_Settings')) {
$activateBtn.on('click', function() {
var data = getLicenseData();
if (!data.license_key || !data.server_url) {
alert('<?php echo esc_js(__('Please enter both license server URL and license key.', 'wc-tier-package-prices')); ?>');
if (!data.license_key || !data.server_url || !data.server_secret) {
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
return;
}