WordPress/wp-includes/class-wp-recovery-mode-key-service.php
Felix Arntz 3a77265148 Bootstrap/Load: Introduce a recovery mode for fixing fatal errors.
Using the new fatal handler introduced in [44962], an email is sent to the admin when a fatal error occurs. This email includes a secret link to enter recovery mode. When clicked, the link will be validated and on success a cookie will be placed on the client, enabling recovery mode for that user. This functionality is executed early before plugins and themes are loaded, in order to be unaffected by potential fatal errors these might be causing.

When in recovery mode, broken plugins and themes will be paused for that client, so that they are able to access the admin backend despite of these errors. They are notified about the broken extensions and the errors caused, and can then decide whether they would like to temporarily deactivate the extension or fix the problem and resume the extension.

A link in the admin bar allows the client to exit recovery mode.

Props timothyblynjacobs, afragen, flixos90, nerrad, miss_jwo, schlessera, spacedmonkey, swissspidy.
Fixes #46130, #44458.

Built from https://develop.svn.wordpress.org/trunk@44973


git-svn-id: http://core.svn.wordpress.org/trunk@44804 1a063a9b-81f0-0310-95a4-ce76da25c4cd
2019-03-21 21:53:51 +00:00

90 lines
2.0 KiB
PHP

<?php
/**
* Error Protection API: WP_Recovery_Mode_Key_service class
*
* @package WordPress
* @since 5.2.0
*/
/**
* Core class used to generate and validate keys used to enter Recovery Mode.
*
* @since 5.2.0
*/
final class WP_Recovery_Mode_Key_Service {
/**
* Creates a recovery mode key.
*
* @since 5.2.0
*
* @global PasswordHash $wp_hasher
*
* @return string Recovery mode key.
*/
public function generate_and_store_recovery_mode_key() {
global $wp_hasher;
$key = wp_generate_password( 22, false );
/**
* Fires when a recovery mode key is generated for a user.
*
* @since 5.2.0
*
* @param string $key The recovery mode key.
*/
do_action( 'generate_recovery_mode_key', $key );
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . WPINC . '/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
$hashed = $wp_hasher->HashPassword( $key );
update_option(
'recovery_key',
array(
'hashed_key' => $hashed,
'created_at' => time(),
)
);
return $key;
}
/**
* Verifies if the recovery mode key is correct.
*
* @since 5.2.0
*
* @param string $key The unhashed key.
* @param int $ttl Time in seconds for the key to be valid for.
* @return true|WP_Error True on success, error object on failure.
*/
public function validate_recovery_mode_key( $key, $ttl ) {
$record = get_option( 'recovery_key' );
if ( ! $record ) {
return new WP_Error( 'no_recovery_key_set', __( 'Recovery Mode not initialized.' ) );
}
if ( ! is_array( $record ) || ! isset( $record['hashed_key'], $record['created_at'] ) ) {
return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) );
}
if ( ! wp_check_password( $key, $record['hashed_key'] ) ) {
return new WP_Error( 'hash_mismatch', __( 'Invalid recovery key.' ) );
}
if ( time() > $record['created_at'] + $ttl ) {
return new WP_Error( 'key_expired', __( 'Recovery key expired.' ) );
}
return true;
}
}