Make WordPress Core

Ticket #46595: 46595.4.patch

File 46595.4.patch, 10.6 KB (added by pbearne, 6 years ago)

lets try this version, remove nonce and now using random string

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

     
    2020         *
    2121         * @global PasswordHash $wp_hasher
    2222         *
    23          * @return string Recovery mode key.
     23         * @return array Recovery mode key and Token.
    2424         */
    2525        public function generate_and_store_recovery_mode_key() {
    2626
     
    4444
    4545                $hashed = $wp_hasher->HashPassword( $key );
    4646
    47                 update_option(
    48                         'recovery_key',
    49                         array(
    50                                 'hashed_key' => $hashed,
    51                                 'created_at' => time(),
    52                         )
     47                $records = get_option( 'recovery_key' );
     48
     49                if ( false === $records ) {
     50
     51                        $records = array();
     52                }
     53
     54                $token = wp_generate_password( 22, false );
     55
     56                $records[ $token ] = array(
     57                        'hashed_key' => $hashed,
     58                        'created_at' => time(),
    5359                );
    5460
    55                 return $key;
     61                update_option( 'recovery_key', $records );
     62
     63                return array( $key, $token );
    5664        }
    5765
    5866        /**
     
    6270         *
    6371         * @param string $key The unhashed key.
    6472         * @param int    $ttl Time in seconds for the key to be valid for.
     73         *
    6574         * @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_recovery_mode_key( $key, $token, $ttl ) {
    6877
    69                 $record = get_option( 'recovery_key' );
     78                $records = get_option( 'recovery_key' );
    7079
    71                 if ( ! $record ) {
     80                if ( ! $records ) {
    7281                        return new WP_Error( 'no_recovery_key_set', __( 'Recovery Mode not initialized.' ) );
    7382                }
    7483
     84                if ( ! isset( $records[ $token ] ) ) {
     85                        return new WP_Error( 'recovery_key_data_missing', __( 'Recovery Mode not initialized.' ) );
     86                }
     87
     88                $record = $records[ $token ];
     89
     90                // remove so it a onetime use
     91                $this->clean_recovery_key_options( $token, $ttl );
     92
    7593                if ( ! is_array( $record ) || ! isset( $record['hashed_key'], $record['created_at'] ) ) {
    7694                        return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) );
    7795                }
     
    86104
    87105                return true;
    88106        }
     107
     108        /**
     109         * Removes old and used recovery keys.
     110         *
     111         * @since 5.2.0
     112         *
     113         *
     114         * @param null $token
     115         *
     116         * @param      $ttl
     117         *
     118         * @return null.
     119         */
     120        public function clean_recovery_key_options( $token = null , $ttl  ){
     121
     122                $records = get_option( 'recovery_key' );
     123
     124                if( null !== $token ){
     125
     126                        unset( $records[ $token ] );
     127                }
     128
     129                foreach ( $records as $key => $record ){
     130                        if ( ! isset($record['created_at']) || time() > $record['created_at'] + $ttl ) {
     131                                unset( $records[ $key ] );
     132                        }
     133                }
     134
     135                update_option( 'recovery_key', $records );
     136        }
    89137}
  • src/wp-includes/class-wp-recovery-mode-link-service.php

     
    1212 * @since 5.2.0
    1313 */
    1414class WP_Recovery_Mode_Link_Service {
    15         const LOGIN_ACTION_ENTER   = 'enter_recovery_mode';
     15        const LOGIN_ACTION_ENTER = 'enter_recovery_mode';
    1616        const LOGIN_ACTION_ENTERED = 'entered_recovery_mode';
    1717
    1818        /**
     
    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_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         *
    100101         * @return string Recovery mode begin URL.
    101102         */
    102         private function get_recovery_mode_begin_url( $key ) {
     103        private function get_recovery_mode_begin_url( $key, $token ) {
    103104
    104105                $url = add_query_arg(
    105106                        array(
    106                                 'action' => self::LOGIN_ACTION_ENTER,
    107                                 'rm_key' => $key,
     107                                'action'   => self::LOGIN_ACTION_ENTER,
     108                                'rm_key'   => $key,
     109                                'rm_token' => $token,
    108110                        ),
    109111                        wp_login_url()
    110112                );
  • 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_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_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_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_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_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_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_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_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_recovery_mode_key( $key, $token, HOUR_IN_SECONDS ) );
     129
     130                // data should be remove by first call
     131                $error  = $service->validate_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_recovery_mode_key( $key, $token, HOUR_IN_SECONDS ) );
     150
     151                // data should be remove by first call
     152                $error  = $service->validate_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}