Make WordPress Core

Ticket #46595: 46595.7.patch

File 46595.7.patch, 12.1 KB (added by pbearne, 6 years ago)

more CS fixes

  • src/wp-includes/class-wp-recovery-mode-key-service.php

     
    88
    99/**
    1010 * Core class used to generate and validate keys used to enter Recovery Mode.
     11 * The token is used to index the array of hashed_key and created_at stored in options
    1112 *
    1213 * @since 5.2.0
    1314 */
     
    2021         *
    2122         * @global PasswordHash $wp_hasher
    2223         *
    23          * @return string Recovery mode key.
     24         * @return array $key, $token Recovery mode key and Token.
    2425         */
    2526        public function generate_and_store_recovery_mode_key() {
    2627
     
    2829
    2930                $key = wp_generate_password( 22, false );
    3031
    31                 /**
    32                  * Fires when a recovery mode key is generated for a user.
    33                  *
    34                  * @since 5.2.0
    35                  *
    36                  * @param string $key The recovery mode key.
    37                  */
    38                 do_action( 'generate_recovery_mode_key', $key );
    3932
     33
    4034                if ( empty( $wp_hasher ) ) {
    4135                        require_once ABSPATH . WPINC . '/class-phpass.php';
    4236                        $wp_hasher = new PasswordHash( 8, true );
     
    4438
    4539                $hashed = $wp_hasher->HashPassword( $key );
    4640
    47                 update_option(
    48                         'recovery_key',
    49                         array(
    50                                 'hashed_key' => $hashed,
    51                                 'created_at' => time(),
    52                         )
     41                $records = get_option( 'recovery_key', array() );
     42
     43                $token = wp_generate_password( 22, false );
     44
     45                $records[ $token ] = array(
     46                        'hashed_key' => $hashed,
     47                        'created_at' => time(),
    5348                );
    5449
    55                 return $key;
     50                update_option( 'recovery_key', $records );
     51
     52                /**
     53                 * Fires when a recovery mode key is generated for a user.
     54                 *
     55                 * @since 5.2.0
     56                 *
     57                 * @param string $key   The recovery mode key.
     58                 * @param string $token The recovery data index.
     59                 */
     60                do_action( 'generate_recovery_mode_key', $key, $token );
     61
     62                return array( $key, $token );
    5663        }
    5764
    5865        /**
    5966         * Verifies if the recovery mode key is correct.
     67         * Removes any old keys and the key passed to it
    6068         *
    6169         * @since 5.2.0
    6270         *
    63          * @param string $key The unhashed key.
    64          * @param int    $ttl Time in seconds for the key to be valid for.
    65          * @return true|WP_Error True on success, error object on failure.
     71         * @param  string $key    The unhashed key.
     72         * @param  string $token  The data index
     73         * @param  int    $ttl    Time in seconds for the key to be valid for.
     74         * @return true|WP_Error  True on success, error object on failure.
    6675         */
    67         public function validate_recovery_mode_key( $key, $ttl ) {
     76        public function validate_and_consume_recovery_mode_key( $key, $token, $ttl ) {
    6877
    69                 $record = get_option( 'recovery_key' );
     78                $records = get_option( 'recovery_key', array() );
    7079
    71                 if ( ! $record ) {
    72                         return new WP_Error( 'no_recovery_key_set', __( 'Recovery Mode not initialized.' ) );
     80                if ( ! isset( $records[ $token ] ) ) {
     81                        return new WP_Error( 'recovery_key_data_missing', __( 'Recovery Mode not initialized.' ) );
    7382                }
    7483
     84                $record = $records[ $token ];
     85
     86                $this->clean_expired_keys( $ttl, $token );
     87
    7588                if ( ! is_array( $record ) || ! isset( $record['hashed_key'], $record['created_at'] ) ) {
    7689                        return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) );
    7790                }
     
    8699
    87100                return true;
    88101        }
     102
     103        /**
     104         * Removes old and used recovery keys.
     105         *
     106         * @since 5.2.0
     107         *
     108         * @param int    $ttl   Number of seconds the links should be valid for
     109         * @param string $token The data index
     110         */
     111        public function clean_expired_keys( $ttl, $token = '' ) {
     112
     113                $records = get_option( 'recovery_key' );
     114
     115                if ( '' !== $token ) {
     116                        unset( $records[ $token ] );
     117                }
     118
     119                foreach ( $records as $key => $record ) {
     120                        if ( ! isset( $record['created_at'] ) || time() > $record['created_at'] + $ttl ) {
     121                                unset( $records[ $key ] );
     122                        }
     123                }
     124
     125                update_option( 'recovery_key', $records );
     126        }
    89127}
  • src/wp-includes/class-wp-recovery-mode-link-service.php

     
    5353         * @return string Generated URL.
    5454         */
    5555        public function generate_url() {
    56                 $key = $this->key_service->generate_and_store_recovery_mode_key();
     56                list( $key, $token ) = $this->key_service->generate_and_store_recovery_mode_key();
    5757
    58                 return $this->get_recovery_mode_begin_url( $key );
     58                return $this->get_recovery_mode_begin_url( $key, $token );
    5959        }
    6060
    6161        /**
     
    7070                        return;
    7171                }
    7272
    73                 if ( ! isset( $_GET['action'], $_GET['rm_key'] ) || self::LOGIN_ACTION_ENTER !== $_GET['action'] ) {
     73                if ( ! isset( $_GET['action'], $_GET['rm_key'], $_GET['rm_token'] ) || self::LOGIN_ACTION_ENTER !== $_GET['action'] ) {
    7474                        return;
    7575                }
    7676
     
    7878                        require_once ABSPATH . WPINC . '/pluggable.php';
    7979                }
    8080
    81                 $validated = $this->key_service->validate_recovery_mode_key( $_GET['rm_key'], $ttl );
     81                $validated = $this->key_service->validate_and_consume_recovery_mode_key( $_GET['rm_key'], $_GET['rm_token'], $ttl );
    8282
    8383                if ( is_wp_error( $validated ) ) {
    8484                        wp_die( $validated, '' );
     
    9797         * @since 5.2.0
    9898         *
    9999         * @param string $key Recovery Mode key created by {@see generate_and_store_recovery_mode_key()}
     100         *
     101         * @param string $token Recovery Mode data index created by {@see generate_and_store_recovery_mode_key()}
     102         *
    100103         * @return string Recovery mode begin URL.
    101104         */
    102         private function get_recovery_mode_begin_url( $key ) {
     105        private function get_recovery_mode_begin_url( $key, $token ) {
    103106
    104107                $url = add_query_arg(
    105108                        array(
    106                                 'action' => self::LOGIN_ACTION_ENTER,
    107                                 'rm_key' => $key,
     109                                'action'   => self::LOGIN_ACTION_ENTER,
     110                                'rm_key'   => $key,
     111                                'rm_token' => $token,
    108112                        ),
    109113                        wp_login_url()
    110114                );
     
    116120                 *
    117121                 * @param string $url
    118122                 * @param string $key
     123                 * @param string $token
    119124                 */
    120                 return apply_filters( 'recovery_mode_begin_url', $url, $key );
     125                return apply_filters( 'recovery_mode_begin_url', $url, $key, $token );
    121126        }
    122127}
  • tests/phpunit/tests/error-protection/recovery-mode-key-service.php

     
    2020         */
    2121        public function test_validate_recovery_mode_key_returns_wp_error_if_no_key_set() {
    2222                $service = new WP_Recovery_Mode_Key_Service();
    23                 $error   = $service->validate_recovery_mode_key( 'abcd', HOUR_IN_SECONDS );
     23                $error   = $service->validate_and_consume_recovery_mode_key( 'abcd', '', HOUR_IN_SECONDS );
    2424
    2525                $this->assertWPError( $error );
    2626                $this->assertEquals( 'no_recovery_key_set', $error->get_error_code() );
    2727        }
     28        /**
     29         * @ticket 46130
     30         */
     31        public function test_validate_recovery_mode_key_returns_wp_error_if_data_missing() {
     32                update_option( 'recovery_key', 'gibberish' );
    2833
     34                $service = new WP_Recovery_Mode_Key_Service();
     35                $error   = $service->validate_and_consume_recovery_mode_key( 'abcd', '', HOUR_IN_SECONDS );
     36
     37                $this->assertWPError( $error );
     38                $this->assertEquals( 'recovery_key_data_missing', $error->get_error_code() );
     39        }
     40
    2941        /**
    3042         * @ticket 46130
    3143         */
     44        public function test_validate_recovery_mode_key_returns_wp_error_if_bad() {
     45                update_option( 'recovery_key', array( 'token' => 'gibberish' ) );
     46
     47                $service = new WP_Recovery_Mode_Key_Service();
     48                $error   = $service->validate_and_consume_recovery_mode_key( 'abcd', 'token', HOUR_IN_SECONDS );
     49
     50                $this->assertWPError( $error );
     51                $this->assertEquals( 'invalid_recovery_key_format', $error->get_error_code() );
     52        }
     53
     54
     55        /**
     56         * @ticket 46130
     57         */
    3258        public function test_validate_recovery_mode_key_returns_wp_error_if_stored_format_is_invalid() {
    33                 update_option( 'recovery_key', 'gibberish' );
    3459
     60                $token =  wp_generate_password( 22, false );
     61                update_option( 'recovery_key', array( $token => 'gibberish' ) );
     62
    3563                $service = new WP_Recovery_Mode_Key_Service();
    36                 $error   = $service->validate_recovery_mode_key( 'abcd', HOUR_IN_SECONDS );
     64                $error   = $service->validate_and_consume_recovery_mode_key( 'abcd', $token, HOUR_IN_SECONDS );
    3765
    3866                $this->assertWPError( $error );
    3967                $this->assertEquals( 'invalid_recovery_key_format', $error->get_error_code() );
     
    4472         */
    4573        public function test_validate_recovery_mode_key_returns_wp_error_if_empty_key() {
    4674                $service = new WP_Recovery_Mode_Key_Service();
    47                 $service->generate_and_store_recovery_mode_key();
    48                 $error = $service->validate_recovery_mode_key( '', HOUR_IN_SECONDS );
     75                list( $key, $token ) = $service->generate_and_store_recovery_mode_key();
     76                $error = $service->validate_and_consume_recovery_mode_key( '', $token, HOUR_IN_SECONDS );
    4977
    5078                $this->assertWPError( $error );
    5179                $this->assertEquals( 'hash_mismatch', $error->get_error_code() );
     
    5684         */
    5785        public function test_validate_recovery_mode_key_returns_wp_error_if_hash_mismatch() {
    5886                $service = new WP_Recovery_Mode_Key_Service();
    59                 $service->generate_and_store_recovery_mode_key();
    60                 $error = $service->validate_recovery_mode_key( 'abcd', HOUR_IN_SECONDS );
     87                list( $key, $token ) = $service->generate_and_store_recovery_mode_key();
     88                $error = $service->validate_and_consume_recovery_mode_key( 'abcd', $token, HOUR_IN_SECONDS );
    6189
    6290                $this->assertWPError( $error );
    6391                $this->assertEquals( 'hash_mismatch', $error->get_error_code() );
     
    6896         */
    6997        public function test_validate_recovery_mode_key_returns_wp_error_if_expired() {
    7098                $service = new WP_Recovery_Mode_Key_Service();
    71                 $key    = $service->generate_and_store_recovery_mode_key();
     99                list( $key, $token ) = $service->generate_and_store_recovery_mode_key();
    72100
    73                 $record               = get_option( 'recovery_key' );
    74                 $record['created_at'] = time() - HOUR_IN_SECONDS - 30;
    75                 update_option( 'recovery_key', $record );
     101                $records                         = get_option( 'recovery_key' );
     102                $records[ $token ]['created_at'] = time() - HOUR_IN_SECONDS - 30;
     103                update_option( 'recovery_key', $records );
    76104
    77                 $error = $service->validate_recovery_mode_key( $key, HOUR_IN_SECONDS );
     105                $error = $service->validate_and_consume_recovery_mode_key( $key, $token, HOUR_IN_SECONDS );
    78106
    79107                $this->assertWPError( $error );
    80108                $this->assertEquals( 'key_expired', $error->get_error_code() );
     
    85113         */
    86114        public function test_validate_recovery_mode_key_returns_true_for_valid_key() {
    87115                $service = new WP_Recovery_Mode_Key_Service();
    88                 $key    = $service->generate_and_store_recovery_mode_key();
    89                 $this->assertTrue( $service->validate_recovery_mode_key( $key, HOUR_IN_SECONDS ) );
     116                list( $key, $token ) = $service->generate_and_store_recovery_mode_key();
     117                $this->assertTrue( $service->validate_and_consume_recovery_mode_key( $key, $token, HOUR_IN_SECONDS ) );
    90118        }
     119
     120
     121        /**
     122         * @ticket 46130
     123         */
     124        public function test_validate_recovery_mode_key_returns_error_if_token_used_more_than_once() {
     125                $service = new WP_Recovery_Mode_Key_Service();
     126                list( $key, $token ) = $service->generate_and_store_recovery_mode_key();
     127
     128                $this->assertTrue( $service->validate_and_consume_recovery_mode_key( $key, $token, HOUR_IN_SECONDS ) );
     129
     130                // data should be remove by first call
     131                $error  = $service->validate_and_consume_recovery_mode_key( $key, $token, HOUR_IN_SECONDS );
     132
     133                $this->assertWPError( $error );
     134                $this->assertEquals( 'no_recovery_key_set', $error->get_error_code() );
     135        }
     136
     137
     138        /**
     139         * @ticket 46130
     140         */
     141        public function test_validate_recovery_mode_key_returns_error_if_token_used_more_than_once_more_than_key_stored() {
     142                $service = new WP_Recovery_Mode_Key_Service();
     143
     144                //              create an extra key
     145                $service->generate_and_store_recovery_mode_key();
     146
     147                list( $key, $token ) = $service->generate_and_store_recovery_mode_key();
     148
     149                $this->assertTrue( $service->validate_and_consume_recovery_mode_key( $key, $token, HOUR_IN_SECONDS ) );
     150
     151                // data should be remove by first call
     152                $error  = $service->validate_and_consume_recovery_mode_key( $key, $token, HOUR_IN_SECONDS );
     153
     154                $this->assertWPError( $error );
     155                $this->assertEquals( 'recovery_key_data_missing', $error->get_error_code() );
     156        }
    91157}