| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 | <?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 {	/**	 * The option name used to store the keys.	 *	 * @since 5.2.0	 * @var string	 */	private $option_name = 'recovery_keys';	/**	 * Creates a recovery mode token.	 *	 * @since 5.2.0	 *	 * @return string $token A random string to identify its associated key in storage.	 */	public function generate_recovery_mode_token() {		return wp_generate_password( 22, false );	}	/**	 * Creates a recovery mode key.	 *	 * @since 5.2.0	 *	 * @global PasswordHash $wp_hasher	 *	 * @param string $token A token generated by {@see generate_recovery_mode_token()}.	 * @return string $key Recovery mode key.	 */	public function generate_and_store_recovery_mode_key( $token ) {		global $wp_hasher;		$key = wp_generate_password( 22, false );		if ( empty( $wp_hasher ) ) {			require_once ABSPATH . WPINC . '/class-phpass.php';			$wp_hasher = new PasswordHash( 8, true );		}		$hashed = $wp_hasher->HashPassword( $key );		$records = $this->get_keys();		$records[ $token ] = array(			'hashed_key' => $hashed,			'created_at' => time(),		);		$this->update_keys( $records );		/**		 * Fires when a recovery mode key is generated.		 *		 * @since 5.2.0		 *		 * @param string $token The recovery data token.		 * @param string $key   The recovery mode key.		 */		do_action( 'generate_recovery_mode_key', $token, $key );		return $key;	}	/**	 * Verifies if the recovery mode key is correct.	 *	 * Recovery mode keys can only be used once; the key will be consumed in the process.	 *	 * @since 5.2.0	 *	 * @param string $token The token used when generating the given key.	 * @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( $token, $key, $ttl ) {		$records = $this->get_keys();		if ( ! isset( $records[ $token ] ) ) {			return new WP_Error( 'token_not_found', __( 'Recovery Mode not initialized.' ) );		}		$record = $records[ $token ];		$this->remove_key( $token );		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;	}	/**	 * Removes expired recovery mode keys.	 *	 * @since 5.2.0	 *	 * @param int $ttl Time in seconds for the keys to be valid for.	 */	public function clean_expired_keys( $ttl ) {		$records = $this->get_keys();		foreach ( $records as $key => $record ) {			if ( ! isset( $record['created_at'] ) || time() > $record['created_at'] + $ttl ) {				unset( $records[ $key ] );			}		}		$this->update_keys( $records );	}	/**	 * Removes a used recovery key.	 *	 * @since 5.2.0	 *	 * @param string $token The token used when generating a recovery mode key.	 */	private function remove_key( $token ) {		$records = $this->get_keys();		if ( ! isset( $records[ $token ] ) ) {			return;		}		unset( $records[ $token ] );		$this->update_keys( $records );	}	/**	 * Gets the recovery key records.	 *	 * @since 5.2.0	 *	 * @return array Associative array of $token => $data pairs, where $data has keys 'hashed_key'	 *               and 'created_at'.	 */	private function get_keys() {		return (array) get_option( $this->option_name, array() );	}	/**	 * Updates the recovery key records.	 *	 * @since 5.2.0	 *	 * @param array $keys Associative array of $token => $data pairs, where $data has keys 'hashed_key'	 *                    and 'created_at'.	 * @return bool True on success, false on failure.	 */	private function update_keys( array $keys ) {		return update_option( $this->option_name, $keys );	}}
 |