mirror of
https://github.com/djav1985/v-wordpress-plugin-updater.git
synced 2025-10-03 16:20:58 +08:00
Merge pull request #97 from djav1985/copilot/fix-9c34f85b-0dbb-4591-a1cc-433f6a705755
Refactor API key management to use database-only storage with automatic refresh
This commit is contained in:
commit
62f2512320
13 changed files with 477 additions and 58 deletions
|
@ -1 +1 @@
|
||||||
{"version":2,"defects":{"Tests\\DatabaseManagerTest::testGetConnectionCreatesFileAndSingleton":7,"Tests\\PluginModelDbTest::testUploadFileTooLargeReturnsError":7,"Tests\\SingleItemSchedulingTest::testPluginUpdaterSchedulesIndividualEvents":7,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingPreventsDoubleBooking":7,"Tests\\SingleItemSchedulingTest::testTransientPreventsConcurrentScheduling":8,"Tests\\SingleItemSchedulingTest::testThemeUpdaterSchedulesIndividualEvents":7,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingHelperFunction":8,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterInstallsAndCleans":7,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterNoUpdateContinues":7,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterStopsOnHttpError":7,"Tests\\UpdaterErrorHandlingTest::testThemeUpdaterStopsOnHttpError":7},"times":{"Tests\\ApiKeyHelperTest::testOptionPersistence":0.002,"Tests\\DatabaseManagerTest::testGetConnectionCreatesFileAndSingleton":0.008,"Tests\\KeyControllerTest::testSendAuthToggleAndDenial":0.004,"Tests\\PluginModelDbTest::testUploadValidZipInsertsRecord":0.003,"Tests\\PluginModelDbTest::testUploadFileTooLargeReturnsError":0.003,"Tests\\PluginModelDbTest::testUploadNonZipReturnsError":0.002,"Tests\\PluginModelDbTest::testDeletePluginReturnsFalseForInvalidFile":0.001,"Tests\\RouterTest::testGetInstanceReturnsSameRouter":0.001,"Tests\\RouterTest::testRedirectRoot":0.042,"Tests\\RouterTest::testNotFoundRoute":0.041,"Tests\\RouterTest::testMethodNotAllowed":0.041,"Tests\\RouterTest::testDispatchesRouteHandler":0.041,"Tests\\RouterTest::testApiRouteMissingParamsRequiresAuth":0.041,"Tests\\SessionManagerTest::testTimeoutExpiryInvalidatesSession":0.04,"Tests\\SessionManagerTest::testUserAgentChangeInvalidatesSession":0.04,"Tests\\SessionManagerTest::testRequireAuthBlocksBlacklistedIp":0.002,"Tests\\SessionManagerTest::testStartCreatesCsrfAndCookieParams":0.001,"Tests\\SessionManagerTest::testRegenerateChangesSessionId":0,"Tests\\SessionManagerTest::testDestroyEndsSession":0,"Tests\\SessionManagerTest::testRequireAuthWithValidSessionSucceeds":0,"Tests\\SingleItemSchedulingTest::testPluginUpdaterSchedulesIndividualEvents":0.001,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingPreventsDoubleBooking":0,"Tests\\SingleItemSchedulingTest::testTransientPreventsConcurrentScheduling":0.001,"Tests\\SingleItemSchedulingTest::testThemeUpdaterSchedulesIndividualEvents":0.001,"Tests\\SingleItemSchedulingTest::testSingleItemCallbacksExist":0,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingHelperFunction":0,"Tests\\UpdaterEncodingTest::testAddQueryArgEncodesOnce":0,"Tests\\UpdaterEncodingTest::testThemeUpdaterHasPluginHeader":0,"Tests\\UpdaterEncodingTest::testPluginUpdaterHasPluginHeader":0,"Tests\\UpdaterEncodingTest::testAddQueryArgReservedCharactersEncodeOnce":0,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterInstallsAndCleans":0.005,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterHandlesWpError":0.002,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterNoUpdateContinues":0.005,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterStopsOnHttpError":0.005,"Tests\\UpdaterErrorHandlingTest::testThemeUpdaterStopsOnHttpError":0.005,"Tests\\UpdaterErrorHandlingTest::testThemeUpdaterHandlesWpError":0.002}}
|
{"version":2,"defects":{"Tests\\DatabaseManagerTest::testGetConnectionCreatesFileAndSingleton":7,"Tests\\PluginModelDbTest::testUploadFileTooLargeReturnsError":7,"Tests\\SingleItemSchedulingTest::testPluginUpdaterSchedulesIndividualEvents":7,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingPreventsDoubleBooking":7,"Tests\\SingleItemSchedulingTest::testTransientPreventsConcurrentScheduling":8,"Tests\\SingleItemSchedulingTest::testThemeUpdaterSchedulesIndividualEvents":7,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingHelperFunction":8,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterInstallsAndCleans":7,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterNoUpdateContinues":7,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterStopsOnHttpError":7,"Tests\\UpdaterErrorHandlingTest::testThemeUpdaterStopsOnHttpError":7,"Tests\\KeyControllerTest::testSendAuthToggleAndDenial":7,"Tests\\RouterTest::testNotFoundRoute":7,"Tests\\RouterTest::testMethodNotAllowed":7,"Tests\\KeyRefreshWorkflowTest::testInitialKeyFetch":7},"times":{"Tests\\ApiKeyHelperTest::testOptionPersistence":0.002,"Tests\\DatabaseManagerTest::testGetConnectionCreatesFileAndSingleton":0.009,"Tests\\KeyControllerTest::testSendAuthToggleAndDenial":0.007,"Tests\\PluginModelDbTest::testUploadValidZipInsertsRecord":0.005,"Tests\\PluginModelDbTest::testUploadFileTooLargeReturnsError":0.004,"Tests\\PluginModelDbTest::testUploadNonZipReturnsError":0.003,"Tests\\PluginModelDbTest::testDeletePluginReturnsFalseForInvalidFile":0.002,"Tests\\RouterTest::testGetInstanceReturnsSameRouter":0.001,"Tests\\RouterTest::testRedirectRoot":0.043,"Tests\\RouterTest::testNotFoundRoute":0.043,"Tests\\RouterTest::testMethodNotAllowed":0.043,"Tests\\RouterTest::testDispatchesRouteHandler":0.043,"Tests\\RouterTest::testApiRouteMissingParamsRequiresAuth":0.043,"Tests\\SessionManagerTest::testTimeoutExpiryInvalidatesSession":0.042,"Tests\\SessionManagerTest::testUserAgentChangeInvalidatesSession":0.041,"Tests\\SessionManagerTest::testRequireAuthBlocksBlacklistedIp":0.003,"Tests\\SessionManagerTest::testStartCreatesCsrfAndCookieParams":0.001,"Tests\\SessionManagerTest::testRegenerateChangesSessionId":0,"Tests\\SessionManagerTest::testDestroyEndsSession":0,"Tests\\SessionManagerTest::testRequireAuthWithValidSessionSucceeds":0,"Tests\\SingleItemSchedulingTest::testPluginUpdaterSchedulesIndividualEvents":0.001,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingPreventsDoubleBooking":0,"Tests\\SingleItemSchedulingTest::testTransientPreventsConcurrentScheduling":0,"Tests\\SingleItemSchedulingTest::testThemeUpdaterSchedulesIndividualEvents":0.001,"Tests\\SingleItemSchedulingTest::testSingleItemCallbacksExist":0,"Tests\\SingleItemSchedulingTest::testUniqueSchedulingHelperFunction":0,"Tests\\UpdaterEncodingTest::testAddQueryArgEncodesOnce":0,"Tests\\UpdaterEncodingTest::testThemeUpdaterHasPluginHeader":0,"Tests\\UpdaterEncodingTest::testPluginUpdaterHasPluginHeader":0,"Tests\\UpdaterEncodingTest::testAddQueryArgReservedCharactersEncodeOnce":0,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterInstallsAndCleans":0.005,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterHandlesWpError":0.002,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterNoUpdateContinues":0.005,"Tests\\UpdaterErrorHandlingTest::testPluginUpdaterStopsOnHttpError":0.005,"Tests\\UpdaterErrorHandlingTest::testThemeUpdaterStopsOnHttpError":0.005,"Tests\\UpdaterErrorHandlingTest::testThemeUpdaterHandlesWpError":0.002,"Tests\\ApiKeyHelperTest::testKeyRefreshFunctionality":0,"Tests\\KeyRefreshWorkflowTest::testInitialKeyFetch":0.005}}
|
|
@ -497,9 +497,8 @@ The v-wordpress-plugin-updater project is designed to streamline the management
|
||||||
|
|
||||||
```php
|
```php
|
||||||
define('VONTMNT_API_URL', 'https://example.com/api');
|
define('VONTMNT_API_URL', 'https://example.com/api');
|
||||||
define('VONTMNT_UPDATE_KEYREGEN', true); // set to true to fetch/regenerate the key
|
|
||||||
```
|
```
|
||||||
The updater will fetch the API key from `/api/key` when this constant is true or when no key is stored. The key is saved as the `vontmnt_api_key` option and `wp-config.php` is rewritten to disable regeneration after the first retrieval.
|
The updater will automatically fetch the API key from `/api/key` when no key is stored. The key is saved as the `vontmnt_api_key` option. When the server indicates a key update is required (via HTTP 401 response), the client will automatically refresh the key using the old key for validation.
|
||||||
6. Ensure the web server user owns the `/storage` directory so uploads and logs can be written. Application logs are written to `LOG_FILE` (default `/storage/logs/app.log`).
|
6. Ensure the web server user owns the `/storage` directory so uploads and logs can be written. Application logs are written to `LOG_FILE` (default `/storage/logs/app.log`).
|
||||||
|
|
||||||
7. From the `update-api/` directory run `php install.php` to create the SQLite database and required tables, including the blacklist. Ensure `storage/updater.sqlite` is writable by the web server.
|
7. From the `update-api/` directory run `php install.php` to create the SQLite database and required tables, including the blacklist. Ensure `storage/updater.sqlite` is writable by the web server.
|
||||||
|
|
|
@ -25,11 +25,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the API key, requesting from the server when needed.
|
* Retrieve the API key, requesting from the server when needed.
|
||||||
|
* Also handles key refresh when server signals an update is required.
|
||||||
*/
|
*/
|
||||||
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
||||||
function vontmnt_get_api_key(): string {
|
function vontmnt_get_api_key(): string {
|
||||||
$key = get_option( 'vontmnt_api_key' );
|
$key = get_option( 'vontmnt_api_key' );
|
||||||
if ( ! $key || ( defined( 'VONTMNT_UPDATE_KEYREGEN' ) && VONTMNT_UPDATE_KEYREGEN ) ) {
|
if ( ! $key ) {
|
||||||
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
$api_url = add_query_arg(
|
$api_url = add_query_arg(
|
||||||
array(
|
array(
|
||||||
|
@ -42,20 +43,42 @@ function vontmnt_get_api_key(): string {
|
||||||
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
$key = wp_remote_retrieve_body( $response );
|
$key = wp_remote_retrieve_body( $response );
|
||||||
update_option( 'vontmnt_api_key', $key, false );
|
update_option( 'vontmnt_api_key', $key, false );
|
||||||
$wp_config = ABSPATH . 'wp-config.php';
|
|
||||||
if ( file_exists( $wp_config ) && is_writable( $wp_config ) ) {
|
|
||||||
$config = file_get_contents( $wp_config );
|
|
||||||
if ( false !== $config ) {
|
|
||||||
$config = preg_replace( "/define\(\s*'VONTMNT_UPDATE_KEYREGEN'\s*,\s*true\s*\);/i", "define('VONTMNT_UPDATE_KEYREGEN', false);", $config );
|
|
||||||
file_put_contents( $wp_config, $config );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return is_string( $key ) ? $key : '';
|
return is_string( $key ) ? $key : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh API key when server indicates an update is needed.
|
||||||
|
*/
|
||||||
|
if ( ! function_exists( 'vontmnt_refresh_api_key' ) ) {
|
||||||
|
function vontmnt_refresh_api_key(): string {
|
||||||
|
$old_key = get_option( 'vontmnt_api_key' );
|
||||||
|
if ( ! $old_key ) {
|
||||||
|
return vontmnt_get_api_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
|
$api_url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'type' => 'auth',
|
||||||
|
'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
|
||||||
|
'old_key' => $old_key,
|
||||||
|
),
|
||||||
|
rtrim( $base, '/' ) . '/key'
|
||||||
|
);
|
||||||
|
$response = wp_remote_get( $api_url );
|
||||||
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
|
$new_key = wp_remote_retrieve_body( $response );
|
||||||
|
update_option( 'vontmnt_api_key', $new_key, false );
|
||||||
|
return $new_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $old_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ZIP package by checking header and optionally testing extraction.
|
* Validate ZIP package by checking header and optionally testing extraction.
|
||||||
*
|
*
|
||||||
|
|
|
@ -25,11 +25,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the API key, requesting from the server when needed.
|
* Retrieve the API key, requesting from the server when needed.
|
||||||
|
* Also handles key refresh when server signals an update is required.
|
||||||
*/
|
*/
|
||||||
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
||||||
function vontmnt_get_api_key(): string {
|
function vontmnt_get_api_key(): string {
|
||||||
$key = get_option( 'vontmnt_api_key' );
|
$key = get_option( 'vontmnt_api_key' );
|
||||||
if ( ! $key || ( defined( 'VONTMNT_UPDATE_KEYREGEN' ) && VONTMNT_UPDATE_KEYREGEN ) ) {
|
if ( ! $key ) {
|
||||||
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
$api_url = add_query_arg(
|
$api_url = add_query_arg(
|
||||||
array(
|
array(
|
||||||
|
@ -42,30 +43,42 @@ function vontmnt_get_api_key(): string {
|
||||||
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
$key = wp_remote_retrieve_body( $response );
|
$key = wp_remote_retrieve_body( $response );
|
||||||
update_option( 'vontmnt_api_key', $key );
|
update_option( 'vontmnt_api_key', $key );
|
||||||
$wp_config = ABSPATH . 'wp-config.php';
|
|
||||||
if ( file_exists( $wp_config ) && is_writable( $wp_config ) ) {
|
|
||||||
$config = file_get_contents( $wp_config );
|
|
||||||
if ( false !== $config ) {
|
|
||||||
$backup = $wp_config . '.bak';
|
|
||||||
if ( ! copy( $wp_config, $backup ) ) {
|
|
||||||
error_log( 'Failed to back up wp-config.php' );
|
|
||||||
return is_string( $key ) ? $key : '';
|
|
||||||
}
|
|
||||||
$updated = preg_replace( "/define\(\s*'VONTMNT_UPDATE_KEYREGEN'\s*,\s*true\s*\);/i", "define('VONTMNT_UPDATE_KEYREGEN', false);", $config, 1, $count );
|
|
||||||
if ( null === $updated || 0 === $count || false === file_put_contents( $wp_config, $updated ) ) {
|
|
||||||
error_log( 'Failed to update VONTMNT_UPDATE_KEYREGEN in wp-config.php' );
|
|
||||||
copy( $backup, $wp_config );
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
unlink( $backup );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return is_string( $key ) ? $key : '';
|
return is_string( $key ) ? $key : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh API key when server indicates an update is needed.
|
||||||
|
*/
|
||||||
|
if ( ! function_exists( 'vontmnt_refresh_api_key' ) ) {
|
||||||
|
function vontmnt_refresh_api_key(): string {
|
||||||
|
$old_key = get_option( 'vontmnt_api_key' );
|
||||||
|
if ( ! $old_key ) {
|
||||||
|
return vontmnt_get_api_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
|
$api_url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'type' => 'auth',
|
||||||
|
'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
|
||||||
|
'old_key' => $old_key,
|
||||||
|
),
|
||||||
|
rtrim( $base, '/' ) . '/key'
|
||||||
|
);
|
||||||
|
$response = wp_remote_get( $api_url );
|
||||||
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
|
$new_key = wp_remote_retrieve_body( $response );
|
||||||
|
update_option( 'vontmnt_api_key', $new_key );
|
||||||
|
return $new_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $old_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ZIP package by checking header and optionally testing extraction.
|
* Validate ZIP package by checking header and optionally testing extraction.
|
||||||
*
|
*
|
||||||
|
@ -309,6 +322,36 @@ function vontmnt_plugin_update_single( string $plugin_path, string $installed_ve
|
||||||
|
|
||||||
$http_code = wp_remote_retrieve_response_code( $response );
|
$http_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
|
||||||
|
if ( 401 === $http_code ) {
|
||||||
|
// Server is signaling key refresh needed
|
||||||
|
$refreshed_key = vontmnt_refresh_api_key();
|
||||||
|
if ( $refreshed_key !== $key ) {
|
||||||
|
// Key was refreshed, retry the request with new key
|
||||||
|
$api_url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'type' => 'plugin',
|
||||||
|
'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
|
||||||
|
'slug' => $plugin_slug,
|
||||||
|
'version' => $installed_version,
|
||||||
|
'key' => $refreshed_key,
|
||||||
|
),
|
||||||
|
VONTMNT_API_URL
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_delete_file( $temp_file );
|
||||||
|
$temp_file = wp_tempnam( $plugin_zip_file );
|
||||||
|
$response = wp_remote_get( $api_url, array( 'stream' => true, 'filename' => $temp_file ) );
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
vontmnt_log_update_context( 'plugin', $plugin_slug, $installed_version, $api_url, 0, 0, 'failed', 'HTTP error: ' . $response->get_error_message() );
|
||||||
|
delete_option( $lock_key );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$http_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( 200 === $http_code && file_exists( $temp_file ) ) {
|
if ( 200 === $http_code && file_exists( $temp_file ) ) {
|
||||||
// Move temp file to final location (allow overwrite)
|
// Move temp file to final location (allow overwrite)
|
||||||
if ( file_exists( $plugin_zip_file ) ) {
|
if ( file_exists( $plugin_zip_file ) ) {
|
||||||
|
|
|
@ -25,11 +25,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the API key, requesting from the server when needed.
|
* Retrieve the API key, requesting from the server when needed.
|
||||||
|
* Also handles key refresh when server signals an update is required.
|
||||||
*/
|
*/
|
||||||
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
||||||
function vontmnt_get_api_key(): string {
|
function vontmnt_get_api_key(): string {
|
||||||
$key = get_option( 'vontmnt_api_key' );
|
$key = get_option( 'vontmnt_api_key' );
|
||||||
if ( ! $key || ( defined( 'VONTMNT_UPDATE_KEYREGEN' ) && VONTMNT_UPDATE_KEYREGEN ) ) {
|
if ( ! $key ) {
|
||||||
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
$api_url = add_query_arg(
|
$api_url = add_query_arg(
|
||||||
array(
|
array(
|
||||||
|
@ -42,20 +43,42 @@ function vontmnt_get_api_key(): string {
|
||||||
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
$key = wp_remote_retrieve_body( $response );
|
$key = wp_remote_retrieve_body( $response );
|
||||||
update_option( 'vontmnt_api_key', $key );
|
update_option( 'vontmnt_api_key', $key );
|
||||||
$wp_config = ABSPATH . 'wp-config.php';
|
|
||||||
if ( file_exists( $wp_config ) && is_writable( $wp_config ) ) {
|
|
||||||
$config = file_get_contents( $wp_config );
|
|
||||||
if ( false !== $config ) {
|
|
||||||
$config = preg_replace( "/define\(\s*'VONTMNT_UPDATE_KEYREGEN'\s*,\s*true\s*\);/i", "define('VONTMNT_UPDATE_KEYREGEN', false);", $config );
|
|
||||||
file_put_contents( $wp_config, $config );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return is_string( $key ) ? $key : '';
|
return is_string( $key ) ? $key : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh API key when server indicates an update is needed.
|
||||||
|
*/
|
||||||
|
if ( ! function_exists( 'vontmnt_refresh_api_key' ) ) {
|
||||||
|
function vontmnt_refresh_api_key(): string {
|
||||||
|
$old_key = get_option( 'vontmnt_api_key' );
|
||||||
|
if ( ! $old_key ) {
|
||||||
|
return vontmnt_get_api_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
|
$api_url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'type' => 'auth',
|
||||||
|
'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
|
||||||
|
'old_key' => $old_key,
|
||||||
|
),
|
||||||
|
rtrim( $base, '/' ) . '/key'
|
||||||
|
);
|
||||||
|
$response = wp_remote_get( $api_url );
|
||||||
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
|
$new_key = wp_remote_retrieve_body( $response );
|
||||||
|
update_option( 'vontmnt_api_key', $new_key );
|
||||||
|
return $new_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $old_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ZIP package by checking header and optionally testing extraction.
|
* Validate ZIP package by checking header and optionally testing extraction.
|
||||||
*
|
*
|
||||||
|
@ -243,6 +266,35 @@ function vontmnt_theme_update_single( string $theme_slug, string $installed_vers
|
||||||
$http_code = wp_remote_retrieve_response_code( $response );
|
$http_code = wp_remote_retrieve_response_code( $response );
|
||||||
$response_body = wp_remote_retrieve_body( $response );
|
$response_body = wp_remote_retrieve_body( $response );
|
||||||
|
|
||||||
|
if ( 401 === $http_code ) {
|
||||||
|
// Server is signaling key refresh needed
|
||||||
|
$key = vontmnt_get_api_key(); // Get the current key for comparison
|
||||||
|
$refreshed_key = vontmnt_refresh_api_key();
|
||||||
|
if ( $refreshed_key !== $key ) {
|
||||||
|
// Key was refreshed, retry the request with new key
|
||||||
|
$api_url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'type' => 'theme',
|
||||||
|
'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
|
||||||
|
'slug' => $theme_slug,
|
||||||
|
'version' => $installed_version,
|
||||||
|
'key' => $refreshed_key,
|
||||||
|
),
|
||||||
|
VONTMNT_API_URL
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = wp_remote_get( $api_url );
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
vontmnt_log_update_context( 'theme', $theme_slug, $installed_version, $api_url, 0, 0, 'failed', 'HTTP error: ' . $response->get_error_message() );
|
||||||
|
delete_option( $lock_key );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$http_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$response_body = wp_remote_retrieve_body( $response );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( $http_code === 200 && ! empty( $response_body ) ) {
|
if ( $http_code === 200 && ! empty( $response_body ) ) {
|
||||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the API key, requesting from the server when needed.
|
* Retrieve the API key, requesting from the server when needed.
|
||||||
|
* Also handles key refresh when server signals an update is required.
|
||||||
*/
|
*/
|
||||||
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
if ( ! function_exists( 'vontmnt_get_api_key' ) ) {
|
||||||
function vontmnt_get_api_key(): string {
|
function vontmnt_get_api_key(): string {
|
||||||
$key = get_option( 'vontmnt_api_key' );
|
$key = get_option( 'vontmnt_api_key' );
|
||||||
if ( ! $key || ( defined( 'VONTMNT_UPDATE_KEYREGEN' ) && VONTMNT_UPDATE_KEYREGEN ) ) {
|
if ( ! $key ) {
|
||||||
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
$api_url = add_query_arg(
|
$api_url = add_query_arg(
|
||||||
array(
|
array(
|
||||||
|
@ -42,20 +43,42 @@ function vontmnt_get_api_key(): string {
|
||||||
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
$key = wp_remote_retrieve_body( $response );
|
$key = wp_remote_retrieve_body( $response );
|
||||||
update_option( 'vontmnt_api_key', $key );
|
update_option( 'vontmnt_api_key', $key );
|
||||||
$wp_config = ABSPATH . 'wp-config.php';
|
|
||||||
if ( file_exists( $wp_config ) && is_writable( $wp_config ) ) {
|
|
||||||
$config = file_get_contents( $wp_config );
|
|
||||||
if ( false !== $config ) {
|
|
||||||
$config = preg_replace( "/define\(\s*'VONTMNT_UPDATE_KEYREGEN'\s*,\s*true\s*\);/i", "define('VONTMNT_UPDATE_KEYREGEN', false);", $config );
|
|
||||||
file_put_contents( $wp_config, $config );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return is_string( $key ) ? $key : '';
|
return is_string( $key ) ? $key : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh API key when server indicates an update is needed.
|
||||||
|
*/
|
||||||
|
if ( ! function_exists( 'vontmnt_refresh_api_key' ) ) {
|
||||||
|
function vontmnt_refresh_api_key(): string {
|
||||||
|
$old_key = get_option( 'vontmnt_api_key' );
|
||||||
|
if ( ! $old_key ) {
|
||||||
|
return vontmnt_get_api_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = defined( 'VONTMNT_API_URL' ) ? VONTMNT_API_URL : '';
|
||||||
|
$api_url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'type' => 'auth',
|
||||||
|
'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
|
||||||
|
'old_key' => $old_key,
|
||||||
|
),
|
||||||
|
rtrim( $base, '/' ) . '/key'
|
||||||
|
);
|
||||||
|
$response = wp_remote_get( $api_url );
|
||||||
|
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||||
|
$new_key = wp_remote_retrieve_body( $response );
|
||||||
|
update_option( 'vontmnt_api_key', $new_key );
|
||||||
|
return $new_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $old_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ZIP package by checking header and optionally testing extraction.
|
* Validate ZIP package by checking header and optionally testing extraction.
|
||||||
|
@ -230,6 +253,35 @@ function vontmnt_theme_update_single( string $theme_slug, string $installed_vers
|
||||||
$http_code = wp_remote_retrieve_response_code( $response );
|
$http_code = wp_remote_retrieve_response_code( $response );
|
||||||
$response_body = wp_remote_retrieve_body( $response );
|
$response_body = wp_remote_retrieve_body( $response );
|
||||||
|
|
||||||
|
if ( 401 === $http_code ) {
|
||||||
|
// Server is signaling key refresh needed
|
||||||
|
$key = vontmnt_get_api_key(); // Get the current key for comparison
|
||||||
|
$refreshed_key = vontmnt_refresh_api_key();
|
||||||
|
if ( $refreshed_key !== $key ) {
|
||||||
|
// Key was refreshed, retry the request with new key
|
||||||
|
$api_url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'type' => 'theme',
|
||||||
|
'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
|
||||||
|
'slug' => $theme_slug,
|
||||||
|
'version' => $installed_version,
|
||||||
|
'key' => $refreshed_key,
|
||||||
|
),
|
||||||
|
VONTMNT_API_URL
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = wp_remote_get( $api_url );
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
vontmnt_log_update_context( 'theme', $theme_slug, $installed_version, $api_url, 0, 0, 'failed', 'HTTP error: ' . $response->get_error_message() );
|
||||||
|
delete_option( $lock_key );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$http_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$response_body = wp_remote_retrieve_body( $response );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( $http_code === 200 && ! empty( $response_body ) ) {
|
if ( $http_code === 200 && ! empty( $response_body ) ) {
|
||||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ class ApiKeyHelperTest extends TestCase
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
define('ABSPATH', sys_get_temp_dir() . '/');
|
define('ABSPATH', sys_get_temp_dir() . '/');
|
||||||
}
|
}
|
||||||
file_put_contents(ABSPATH . 'wp-config.php', "<?php\ndefine('VONTMNT_UPDATE_KEYREGEN', true);\n");
|
file_put_contents(ABSPATH . 'wp-config.php', "<?php\n// Test config file\n");
|
||||||
if (!defined('VONTMNT_API_URL')) {
|
if (!defined('VONTMNT_API_URL')) {
|
||||||
define('VONTMNT_API_URL', 'https://example.com/api');
|
define('VONTMNT_API_URL', 'https://example.com/api');
|
||||||
}
|
}
|
||||||
|
@ -53,11 +53,25 @@ class ApiKeyHelperTest extends TestCase
|
||||||
$key1 = \vontmnt_get_api_key();
|
$key1 = \vontmnt_get_api_key();
|
||||||
$this->assertSame('secret', $key1);
|
$this->assertSame('secret', $key1);
|
||||||
$this->assertSame(1, $remote_calls);
|
$this->assertSame(1, $remote_calls);
|
||||||
$content = file_get_contents(ABSPATH . 'wp-config.php');
|
|
||||||
$this->assertStringContainsString("VONTMNT_UPDATE_KEYREGEN', false", $content);
|
|
||||||
$key2 = \vontmnt_get_api_key();
|
$key2 = \vontmnt_get_api_key();
|
||||||
$this->assertSame('secret', $key2);
|
$this->assertSame('secret', $key2);
|
||||||
$this->assertSame(1, $remote_calls);
|
$this->assertSame(1, $remote_calls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testKeyRefreshFunctionality(): void
|
||||||
|
{
|
||||||
|
global $options, $remote_calls;
|
||||||
|
require_once __DIR__ . '/../mu-plugin/v-sys-plugin-updater.php';
|
||||||
|
|
||||||
|
// Set up initial key
|
||||||
|
$options['vontmnt_api_key'] = 'old-key-123';
|
||||||
|
$remote_calls = 0;
|
||||||
|
|
||||||
|
// Test refresh functionality
|
||||||
|
$refreshed_key = \vontmnt_refresh_api_key();
|
||||||
|
$this->assertSame('secret', $refreshed_key);
|
||||||
|
$this->assertSame(1, $remote_calls);
|
||||||
|
$this->assertSame('secret', $options['vontmnt_api_key']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
156
tests/KeyRefreshWorkflowTest.php
Normal file
156
tests/KeyRefreshWorkflowTest.php
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Mock WordPress functions needed for the test
|
||||||
|
$key_refresh_test_options = [];
|
||||||
|
$key_refresh_test_remote_calls = [];
|
||||||
|
|
||||||
|
if (!function_exists('get_option')) {
|
||||||
|
function get_option($name) {
|
||||||
|
global $key_refresh_test_options;
|
||||||
|
return $key_refresh_test_options[$name] ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('update_option')) {
|
||||||
|
function update_option($name, $value, $autoload = null) {
|
||||||
|
global $key_refresh_test_options;
|
||||||
|
$key_refresh_test_options[$name] = $value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('wp_parse_url')) {
|
||||||
|
function wp_parse_url($url, $component) {
|
||||||
|
return 'example.com';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('site_url')) {
|
||||||
|
function site_url() {
|
||||||
|
return 'https://example.com';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('add_query_arg')) {
|
||||||
|
function add_query_arg($args, $url) {
|
||||||
|
$query = http_build_query($args, '', '&', PHP_QUERY_RFC3986);
|
||||||
|
return rtrim($url, '/') . '/key?' . $query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('wp_remote_get')) {
|
||||||
|
function wp_remote_get($url) {
|
||||||
|
global $key_refresh_test_remote_calls;
|
||||||
|
$key_refresh_test_remote_calls[] = $url;
|
||||||
|
|
||||||
|
// Simulate different responses based on URL parameters
|
||||||
|
if (strpos($url, 'old_key=old-key-123') !== false) {
|
||||||
|
// This is a key refresh request with old key
|
||||||
|
return ['body' => 'new-refreshed-key-456', 'response' => ['code' => 200]];
|
||||||
|
} else {
|
||||||
|
// Standard key request
|
||||||
|
return ['body' => 'initial-key-789', 'response' => ['code' => 200]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('wp_remote_retrieve_response_code')) {
|
||||||
|
function wp_remote_retrieve_response_code($response) {
|
||||||
|
return $response['response']['code'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('wp_remote_retrieve_body')) {
|
||||||
|
function wp_remote_retrieve_body($response) {
|
||||||
|
return $response['body'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('is_wp_error')) {
|
||||||
|
function is_wp_error($thing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('VONTMNT_API_URL')) {
|
||||||
|
define('VONTMNT_API_URL', 'https://example.com/api');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Tests {
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class KeyRefreshWorkflowTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
global $key_refresh_test_options, $key_refresh_test_remote_calls;
|
||||||
|
$key_refresh_test_options = [];
|
||||||
|
$key_refresh_test_remote_calls = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInitialKeyFetch(): void
|
||||||
|
{
|
||||||
|
global $key_refresh_test_remote_calls;
|
||||||
|
|
||||||
|
// Clear any existing options
|
||||||
|
global $key_refresh_test_options;
|
||||||
|
$key_refresh_test_options = [];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../mu-plugin/v-sys-plugin-updater.php';
|
||||||
|
|
||||||
|
// Test initial key fetch (no key stored)
|
||||||
|
$key = \vontmnt_get_api_key();
|
||||||
|
|
||||||
|
$this->assertSame('initial-key-789', $key);
|
||||||
|
$this->assertCount(1, $key_refresh_test_remote_calls);
|
||||||
|
$this->assertStringContainsString('type=auth', $key_refresh_test_remote_calls[0]);
|
||||||
|
$this->assertStringContainsString('domain=example.com', $key_refresh_test_remote_calls[0]);
|
||||||
|
$this->assertStringNotContainsString('old_key', $key_refresh_test_remote_calls[0]);
|
||||||
|
|
||||||
|
// Verify key was stored
|
||||||
|
$this->assertSame('initial-key-789', $key_refresh_test_options['vontmnt_api_key']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testKeyRefreshWorkflow(): void
|
||||||
|
{
|
||||||
|
global $key_refresh_test_options, $key_refresh_test_remote_calls;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../mu-plugin/v-sys-plugin-updater.php';
|
||||||
|
|
||||||
|
// Set up scenario where old key exists
|
||||||
|
$key_refresh_test_options['vontmnt_api_key'] = 'old-key-123';
|
||||||
|
|
||||||
|
// Test key refresh
|
||||||
|
$new_key = \vontmnt_refresh_api_key();
|
||||||
|
|
||||||
|
$this->assertSame('new-refreshed-key-456', $new_key);
|
||||||
|
$this->assertCount(1, $key_refresh_test_remote_calls);
|
||||||
|
$this->assertStringContainsString('type=auth', $key_refresh_test_remote_calls[0]);
|
||||||
|
$this->assertStringContainsString('domain=example.com', $key_refresh_test_remote_calls[0]);
|
||||||
|
$this->assertStringContainsString('old_key=old-key-123', $key_refresh_test_remote_calls[0]);
|
||||||
|
|
||||||
|
// Verify key was updated in options
|
||||||
|
$this->assertSame('new-refreshed-key-456', $key_refresh_test_options['vontmnt_api_key']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubsequentKeyGetUsesStoredKey(): void
|
||||||
|
{
|
||||||
|
global $key_refresh_test_options, $key_refresh_test_remote_calls;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../mu-plugin/v-sys-plugin-updater.php';
|
||||||
|
|
||||||
|
// Set up scenario where key already exists
|
||||||
|
$key_refresh_test_options['vontmnt_api_key'] = 'existing-key';
|
||||||
|
|
||||||
|
// Test that existing key is returned without remote call
|
||||||
|
$key = \vontmnt_get_api_key();
|
||||||
|
|
||||||
|
$this->assertSame('existing-key', $key);
|
||||||
|
$this->assertCount(0, $key_refresh_test_remote_calls); // No remote calls should be made
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -106,6 +106,13 @@ class ApiController extends Controller
|
||||||
ErrorManager::getInstance()->log($domain . ' ' . date('Y-m-d') . ' Successful', 'info');
|
ErrorManager::getInstance()->log($domain . ' ' . date('Y-m-d') . ' Successful', 'info');
|
||||||
return new Response(204);
|
return new Response(204);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Key mismatch - check if key update is pending
|
||||||
|
if (HostsModel::isKeyUpdatePending($domain)) {
|
||||||
|
// Signal client to refresh key with 401 Unauthorized
|
||||||
|
ErrorManager::getInstance()->log($domain . ' Key update required');
|
||||||
|
return new Response(401);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,24 @@ class KeyController extends Controller
|
||||||
return new Response(400);
|
return new Response(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a key refresh request (includes old_key parameter)
|
||||||
|
if (isset($_GET['old_key']) && $_GET['old_key'] !== '') {
|
||||||
|
$oldKey = Validation::validateKey($_GET['old_key']);
|
||||||
|
if ($oldKey === null) {
|
||||||
|
ErrorManager::getInstance()->log('Bad request invalid parameter: old_key');
|
||||||
|
return new Response(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newKey = HostsModel::validateAndCompleteKeyUpdate($domain, $oldKey);
|
||||||
|
if ($newKey !== null) {
|
||||||
|
return Response::text($newKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorManager::getInstance()->log('Key refresh failed for domain: ' . $domain);
|
||||||
|
return new Response(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard key request
|
||||||
$key = HostsModel::getKeyIfSendAuth($domain);
|
$key = HostsModel::getKeyIfSendAuth($domain);
|
||||||
if ($key !== null) {
|
if ($key !== null) {
|
||||||
return Response::text($key);
|
return Response::text($key);
|
||||||
|
|
|
@ -90,4 +90,58 @@ class HostsModel
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a key update is pending for a domain.
|
||||||
|
*/
|
||||||
|
public static function isKeyUpdatePending(string $domain): bool
|
||||||
|
{
|
||||||
|
$conn = DatabaseManager::getConnection();
|
||||||
|
$row = $conn->fetchAssociative('SELECT old_key FROM hosts WHERE domain = ? AND old_key IS NOT NULL AND old_key != ""', [$domain]);
|
||||||
|
return $row !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate key update by setting send_auth and storing old key.
|
||||||
|
*/
|
||||||
|
public static function initiateKeyUpdate(string $domain, string $newKey): bool
|
||||||
|
{
|
||||||
|
$conn = DatabaseManager::getConnection();
|
||||||
|
// First get the current key to store as old key
|
||||||
|
$row = $conn->fetchAssociative('SELECT key FROM hosts WHERE domain = ?', [$domain]);
|
||||||
|
if (!$row) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldKey = $row['key'];
|
||||||
|
$newEncryptedKey = Encryption::encrypt($newKey);
|
||||||
|
|
||||||
|
// Update with new key, store old key, and set send_auth
|
||||||
|
return $conn->executeStatement(
|
||||||
|
'UPDATE hosts SET key = ?, old_key = ?, send_auth = 1 WHERE domain = ?',
|
||||||
|
[$newEncryptedKey, $oldKey, $domain]
|
||||||
|
) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate old key and complete key update process.
|
||||||
|
*/
|
||||||
|
public static function validateAndCompleteKeyUpdate(string $domain, string $providedOldKey): ?string
|
||||||
|
{
|
||||||
|
$conn = DatabaseManager::getConnection();
|
||||||
|
$row = $conn->fetchAssociative('SELECT key, old_key FROM hosts WHERE domain = ?', [$domain]);
|
||||||
|
|
||||||
|
if (!$row || !$row['old_key']) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$storedOldKey = Encryption::decrypt($row['old_key']);
|
||||||
|
if ($storedOldKey === $providedOldKey) {
|
||||||
|
// Clear old_key and return new key
|
||||||
|
$conn->executeStatement('UPDATE hosts SET old_key = NULL WHERE domain = ?', [$domain]);
|
||||||
|
return Encryption::decrypt($row['key']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ try {
|
||||||
$hosts = $schema->createTable('hosts');
|
$hosts = $schema->createTable('hosts');
|
||||||
$hosts->addColumn('domain', 'text');
|
$hosts->addColumn('domain', 'text');
|
||||||
$hosts->addColumn('key', 'text');
|
$hosts->addColumn('key', 'text');
|
||||||
|
$hosts->addColumn('old_key', 'text', ['notnull' => false]);
|
||||||
$hosts->addColumn('send_auth', 'boolean', ['default' => 0]);
|
$hosts->addColumn('send_auth', 'boolean', ['default' => 0]);
|
||||||
$hosts->setPrimaryKey(['domain']);
|
$hosts->setPrimaryKey(['domain']);
|
||||||
|
|
||||||
|
|
12
vendor/composer/installed.php
vendored
12
vendor/composer/installed.php
vendored
|
@ -1,9 +1,9 @@
|
||||||
<?php return array(
|
<?php return array(
|
||||||
'root' => array(
|
'root' => array(
|
||||||
'name' => '__root__',
|
'name' => '__root__',
|
||||||
'pretty_version' => 'dev-copilot/fix-d4bad72a-26e6-46d2-919d-20b97bcf1178',
|
'pretty_version' => 'dev-copilot/fix-9c34f85b-0dbb-4591-a1cc-433f6a705755',
|
||||||
'version' => 'dev-copilot/fix-d4bad72a-26e6-46d2-919d-20b97bcf1178',
|
'version' => 'dev-copilot/fix-9c34f85b-0dbb-4591-a1cc-433f6a705755',
|
||||||
'reference' => 'd8902d3ac148ce9819bcfa378252ab93e6cc311c',
|
'reference' => '24f354d78cc7e83b67d2b5637a82aa7f62c0847d',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
@ -11,9 +11,9 @@
|
||||||
),
|
),
|
||||||
'versions' => array(
|
'versions' => array(
|
||||||
'__root__' => array(
|
'__root__' => array(
|
||||||
'pretty_version' => 'dev-copilot/fix-d4bad72a-26e6-46d2-919d-20b97bcf1178',
|
'pretty_version' => 'dev-copilot/fix-9c34f85b-0dbb-4591-a1cc-433f6a705755',
|
||||||
'version' => 'dev-copilot/fix-d4bad72a-26e6-46d2-919d-20b97bcf1178',
|
'version' => 'dev-copilot/fix-9c34f85b-0dbb-4591-a1cc-433f6a705755',
|
||||||
'reference' => 'd8902d3ac148ce9819bcfa378252ab93e6cc311c',
|
'reference' => '24f354d78cc7e83b67d2b5637a82aa7f62c0847d',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue