WP_REST_Server::CREATABLE, 'callback' => array( $this, 'rest_generate_codes' ), 'permission_callback' => function( $request ) { return Two_Factor_Core::rest_api_can_edit_user_and_update_two_factor_options( $request['user_id'] ); }, 'args' => array( 'user_id' => array( 'required' => true, 'type' => 'integer', ), 'enable_provider' => array( 'required' => false, 'type' => 'boolean', 'default' => false, ), ), ) ); } /** * Displays an admin notice when backup codes have run out. * * @since 0.1-dev * * @codeCoverageIgnore */ public function admin_notices() { $user = wp_get_current_user(); // Return if the provider is not enabled. if ( ! in_array( __CLASS__, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ), true ) ) { return; } // Return if we are not out of codes. if ( $this->is_available_for_user( $user ) ) { return; } ?>

regenerate!', 'two-factor' ), esc_url( get_edit_user_link( $user->ID ) . '#two-factor-backup-codes' ) ), array( 'a' => array( 'href' => true ) ) ); ?>

ID, self::BACKUP_CODES_META_KEY, true ); } for ( $i = 0; $i < $num_codes; $i++ ) { $code = $this->get_code(); $codes_hashed[] = wp_hash_password( $code ); $codes[] = $code; unset( $code ); } update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $codes_hashed ); // Unhashed. return $codes; } /** * Generates Backup Codes for returning through the WordPress Rest API. * * @since 0.8.0 */ public function rest_generate_codes( $request ) { $user_id = $request['user_id']; $user = get_user_by( 'id', $user_id ); // Hardcode these, the user shouldn't be able to choose them. $args = array( 'number' => self::NUMBER_OF_CODES, 'method' => 'replace', ); // Setup the return data. $codes = $this->generate_codes( $user, $args ); $count = self::codes_remaining_for_user( $user ); $title = sprintf( /* translators: %s: the site's domain */ __( 'Two-Factor Recovery Codes for %s', 'two-factor' ), home_url( '/' ) ); // Generate download content. $download_link = 'data:application/text;charset=utf-8,'; $download_link .= rawurlencode( "{$title}\r\n\r\n" ); $i = 1; foreach ( $codes as $code ) { $download_link .= rawurlencode( "{$i}. {$code}\r\n" ); $i++; } $i18n = array( /* translators: %s: count */ 'count' => esc_html( sprintf( _n( '%s unused code remaining, each recovery code can only be used once.', '%s unused codes remaining, each recovery code can only be used once.', $count, 'two-factor' ), $count ) ), ); if ( $request->get_param( 'enable_provider' ) && ! Two_Factor_Core::enable_provider_for_user( $user_id, 'Two_Factor_Backup_Codes' ) ) { return new WP_Error( 'db_error', __( 'Unable to enable recovery codes for this user.', 'two-factor' ), array( 'status' => 500 ) ); } return array( 'codes' => $codes, 'download_link' => $download_link, 'remaining' => $count, 'i18n' => $i18n, ); } /** * Returns the number of unused codes for the specified user * * @param WP_User $user WP_User object of the logged-in user. * @return int $int The number of unused codes remaining */ public static function codes_remaining_for_user( $user ) { $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) { return count( $backup_codes ); } return 0; } /** * Prints the form that prompts the user to authenticate. * * @since 0.1-dev * * @param WP_User $user WP_User object of the logged-in user. */ public function authentication_page( $user ) { require_once ABSPATH . '/wp-admin/includes/template.php'; ?>


sanitize_code_from_request( 'two-factor-backup-code' ); if ( ! $backup_code ) { return false; } return $this->validate_code( $user, $backup_code ); } /** * Validates a backup code. * * Backup Codes are single use and are deleted upon a successful validation. * * @since 0.1-dev * * @param WP_User $user WP_User object of the logged-in user. * @param int $code The backup code. * @return boolean */ public function validate_code( $user, $code ) { $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) { foreach ( $backup_codes as $code_index => $code_hashed ) { if ( wp_check_password( $code, $code_hashed, $user->ID ) ) { $this->delete_code( $user, $code_hashed ); return true; } } } return false; } /** * Deletes a backup code. * * @since 0.1-dev * * @param WP_User $user WP_User object of the logged-in user. * @param string $code_hashed The hashed the backup code. */ public function delete_code( $user, $code_hashed ) { $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); // Delete the current code from the list since it's been used. $backup_codes = array_flip( $backup_codes ); unset( $backup_codes[ $code_hashed ] ); $backup_codes = array_values( array_flip( $backup_codes ) ); // Update the backup code master list. update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $backup_codes ); } }