wp/wp-includes/class-wp-recovery-mode-key-service.php
changeset 9 177826044cd9
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
       
     1 <?php
       
     2 /**
       
     3  * Error Protection API: WP_Recovery_Mode_Key_Service class
       
     4  *
       
     5  * @package WordPress
       
     6  * @since   5.2.0
       
     7  */
       
     8 
       
     9 /**
       
    10  * Core class used to generate and validate keys used to enter Recovery Mode.
       
    11  *
       
    12  * @since 5.2.0
       
    13  */
       
    14 final class WP_Recovery_Mode_Key_Service {
       
    15 
       
    16 	/**
       
    17 	 * The option name used to store the keys.
       
    18 	 *
       
    19 	 * @since 5.2.0
       
    20 	 * @var string
       
    21 	 */
       
    22 	private $option_name = 'recovery_keys';
       
    23 
       
    24 	/**
       
    25 	 * Creates a recovery mode token.
       
    26 	 *
       
    27 	 * @since 5.2.0
       
    28 	 *
       
    29 	 * @return string $token A random string to identify its associated key in storage.
       
    30 	 */
       
    31 	public function generate_recovery_mode_token() {
       
    32 		return wp_generate_password( 22, false );
       
    33 	}
       
    34 
       
    35 	/**
       
    36 	 * Creates a recovery mode key.
       
    37 	 *
       
    38 	 * @since 5.2.0
       
    39 	 *
       
    40 	 * @global PasswordHash $wp_hasher
       
    41 	 *
       
    42 	 * @param string $token A token generated by {@see generate_recovery_mode_token()}.
       
    43 	 * @return string $key Recovery mode key.
       
    44 	 */
       
    45 	public function generate_and_store_recovery_mode_key( $token ) {
       
    46 
       
    47 		global $wp_hasher;
       
    48 
       
    49 		$key = wp_generate_password( 22, false );
       
    50 
       
    51 		if ( empty( $wp_hasher ) ) {
       
    52 			require_once ABSPATH . WPINC . '/class-phpass.php';
       
    53 			$wp_hasher = new PasswordHash( 8, true );
       
    54 		}
       
    55 
       
    56 		$hashed = $wp_hasher->HashPassword( $key );
       
    57 
       
    58 		$records = $this->get_keys();
       
    59 
       
    60 		$records[ $token ] = array(
       
    61 			'hashed_key' => $hashed,
       
    62 			'created_at' => time(),
       
    63 		);
       
    64 
       
    65 		$this->update_keys( $records );
       
    66 
       
    67 		/**
       
    68 		 * Fires when a recovery mode key is generated.
       
    69 		 *
       
    70 		 * @since 5.2.0
       
    71 		 *
       
    72 		 * @param string $token The recovery data token.
       
    73 		 * @param string $key   The recovery mode key.
       
    74 		 */
       
    75 		do_action( 'generate_recovery_mode_key', $token, $key );
       
    76 
       
    77 		return $key;
       
    78 	}
       
    79 
       
    80 	/**
       
    81 	 * Verifies if the recovery mode key is correct.
       
    82 	 *
       
    83 	 * Recovery mode keys can only be used once; the key will be consumed in the process.
       
    84 	 *
       
    85 	 * @since 5.2.0
       
    86 	 *
       
    87 	 * @param string $token The token used when generating the given key.
       
    88 	 * @param string $key   The unhashed key.
       
    89 	 * @param int    $ttl   Time in seconds for the key to be valid for.
       
    90 	 * @return true|WP_Error True on success, error object on failure.
       
    91 	 */
       
    92 	public function validate_recovery_mode_key( $token, $key, $ttl ) {
       
    93 
       
    94 		$records = $this->get_keys();
       
    95 
       
    96 		if ( ! isset( $records[ $token ] ) ) {
       
    97 			return new WP_Error( 'token_not_found', __( 'Recovery Mode not initialized.' ) );
       
    98 		}
       
    99 
       
   100 		$record = $records[ $token ];
       
   101 
       
   102 		$this->remove_key( $token );
       
   103 
       
   104 		if ( ! is_array( $record ) || ! isset( $record['hashed_key'], $record['created_at'] ) ) {
       
   105 			return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) );
       
   106 		}
       
   107 
       
   108 		if ( ! wp_check_password( $key, $record['hashed_key'] ) ) {
       
   109 			return new WP_Error( 'hash_mismatch', __( 'Invalid recovery key.' ) );
       
   110 		}
       
   111 
       
   112 		if ( time() > $record['created_at'] + $ttl ) {
       
   113 			return new WP_Error( 'key_expired', __( 'Recovery key expired.' ) );
       
   114 		}
       
   115 
       
   116 		return true;
       
   117 	}
       
   118 
       
   119 	/**
       
   120 	 * Removes expired recovery mode keys.
       
   121 	 *
       
   122 	 * @since 5.2.0
       
   123 	 *
       
   124 	 * @param int $ttl Time in seconds for the keys to be valid for.
       
   125 	 */
       
   126 	public function clean_expired_keys( $ttl ) {
       
   127 
       
   128 		$records = $this->get_keys();
       
   129 
       
   130 		foreach ( $records as $key => $record ) {
       
   131 			if ( ! isset( $record['created_at'] ) || time() > $record['created_at'] + $ttl ) {
       
   132 				unset( $records[ $key ] );
       
   133 			}
       
   134 		}
       
   135 
       
   136 		$this->update_keys( $records );
       
   137 	}
       
   138 
       
   139 	/**
       
   140 	 * Removes a used recovery key.
       
   141 	 *
       
   142 	 * @since 5.2.0
       
   143 	 *
       
   144 	 * @param string $token The token used when generating a recovery mode key.
       
   145 	 */
       
   146 	private function remove_key( $token ) {
       
   147 
       
   148 		$records = $this->get_keys();
       
   149 
       
   150 		if ( ! isset( $records[ $token ] ) ) {
       
   151 			return;
       
   152 		}
       
   153 
       
   154 		unset( $records[ $token ] );
       
   155 
       
   156 		$this->update_keys( $records );
       
   157 	}
       
   158 
       
   159 	/**
       
   160 	 * Gets the recovery key records.
       
   161 	 *
       
   162 	 * @since 5.2.0
       
   163 	 *
       
   164 	 * @return array Associative array of $token => $data pairs, where $data has keys 'hashed_key'
       
   165 	 *               and 'created_at'.
       
   166 	 */
       
   167 	private function get_keys() {
       
   168 		return (array) get_option( $this->option_name, array() );
       
   169 	}
       
   170 
       
   171 	/**
       
   172 	 * Updates the recovery key records.
       
   173 	 *
       
   174 	 * @since 5.2.0
       
   175 	 *
       
   176 	 * @param array $keys Associative array of $token => $data pairs, where $data has keys 'hashed_key'
       
   177 	 *                    and 'created_at'.
       
   178 	 * @return bool True on success, false on failure.
       
   179 	 */
       
   180 	private function update_keys( array $keys ) {
       
   181 		return update_option( $this->option_name, $keys );
       
   182 	}
       
   183 }