Ticket #46595: 46595.7.patch
File 46595.7.patch, 12.1 KB (added by , 6 years ago) |
---|
-
src/wp-includes/class-wp-recovery-mode-key-service.php
8 8 9 9 /** 10 10 * 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 11 12 * 12 13 * @since 5.2.0 13 14 */ … … 20 21 * 21 22 * @global PasswordHash $wp_hasher 22 23 * 23 * @return string Recovery mode key.24 * @return array $key, $token Recovery mode key and Token. 24 25 */ 25 26 public function generate_and_store_recovery_mode_key() { 26 27 … … 28 29 29 30 $key = wp_generate_password( 22, false ); 30 31 31 /**32 * Fires when a recovery mode key is generated for a user.33 *34 * @since 5.2.035 *36 * @param string $key The recovery mode key.37 */38 do_action( 'generate_recovery_mode_key', $key );39 32 33 40 34 if ( empty( $wp_hasher ) ) { 41 35 require_once ABSPATH . WPINC . '/class-phpass.php'; 42 36 $wp_hasher = new PasswordHash( 8, true ); … … 44 38 45 39 $hashed = $wp_hasher->HashPassword( $key ); 46 40 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(), 53 48 ); 54 49 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 ); 56 63 } 57 64 58 65 /** 59 66 * Verifies if the recovery mode key is correct. 67 * Removes any old keys and the key passed to it 60 68 * 61 69 * @since 5.2.0 62 70 * 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. 66 75 */ 67 public function validate_ recovery_mode_key( $key, $ttl ) {76 public function validate_and_consume_recovery_mode_key( $key, $token, $ttl ) { 68 77 69 $record = get_option( 'recovery_key');78 $records = get_option( 'recovery_key', array() ); 70 79 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.' ) ); 73 82 } 74 83 84 $record = $records[ $token ]; 85 86 $this->clean_expired_keys( $ttl, $token ); 87 75 88 if ( ! is_array( $record ) || ! isset( $record['hashed_key'], $record['created_at'] ) ) { 76 89 return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) ); 77 90 } … … 86 99 87 100 return true; 88 101 } 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 } 89 127 } -
src/wp-includes/class-wp-recovery-mode-link-service.php
53 53 * @return string Generated URL. 54 54 */ 55 55 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(); 57 57 58 return $this->get_recovery_mode_begin_url( $key );58 return $this->get_recovery_mode_begin_url( $key, $token ); 59 59 } 60 60 61 61 /** … … 70 70 return; 71 71 } 72 72 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'] ) { 74 74 return; 75 75 } 76 76 … … 78 78 require_once ABSPATH . WPINC . '/pluggable.php'; 79 79 } 80 80 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 ); 82 82 83 83 if ( is_wp_error( $validated ) ) { 84 84 wp_die( $validated, '' ); … … 97 97 * @since 5.2.0 98 98 * 99 99 * @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 * 100 103 * @return string Recovery mode begin URL. 101 104 */ 102 private function get_recovery_mode_begin_url( $key ) {105 private function get_recovery_mode_begin_url( $key, $token ) { 103 106 104 107 $url = add_query_arg( 105 108 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, 108 112 ), 109 113 wp_login_url() 110 114 ); … … 116 120 * 117 121 * @param string $url 118 122 * @param string $key 123 * @param string $token 119 124 */ 120 return apply_filters( 'recovery_mode_begin_url', $url, $key );125 return apply_filters( 'recovery_mode_begin_url', $url, $key, $token ); 121 126 } 122 127 } -
tests/phpunit/tests/error-protection/recovery-mode-key-service.php
20 20 */ 21 21 public function test_validate_recovery_mode_key_returns_wp_error_if_no_key_set() { 22 22 $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 ); 24 24 25 25 $this->assertWPError( $error ); 26 26 $this->assertEquals( 'no_recovery_key_set', $error->get_error_code() ); 27 27 } 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' ); 28 33 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 29 41 /** 30 42 * @ticket 46130 31 43 */ 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 */ 32 58 public function test_validate_recovery_mode_key_returns_wp_error_if_stored_format_is_invalid() { 33 update_option( 'recovery_key', 'gibberish' );34 59 60 $token = wp_generate_password( 22, false ); 61 update_option( 'recovery_key', array( $token => 'gibberish' ) ); 62 35 63 $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 ); 37 65 38 66 $this->assertWPError( $error ); 39 67 $this->assertEquals( 'invalid_recovery_key_format', $error->get_error_code() ); … … 44 72 */ 45 73 public function test_validate_recovery_mode_key_returns_wp_error_if_empty_key() { 46 74 $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 ); 49 77 50 78 $this->assertWPError( $error ); 51 79 $this->assertEquals( 'hash_mismatch', $error->get_error_code() ); … … 56 84 */ 57 85 public function test_validate_recovery_mode_key_returns_wp_error_if_hash_mismatch() { 58 86 $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 ); 61 89 62 90 $this->assertWPError( $error ); 63 91 $this->assertEquals( 'hash_mismatch', $error->get_error_code() ); … … 68 96 */ 69 97 public function test_validate_recovery_mode_key_returns_wp_error_if_expired() { 70 98 $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(); 72 100 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 ); 76 104 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 ); 78 106 79 107 $this->assertWPError( $error ); 80 108 $this->assertEquals( 'key_expired', $error->get_error_code() ); … … 85 113 */ 86 114 public function test_validate_recovery_mode_key_returns_true_for_valid_key() { 87 115 $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 ) ); 90 118 } 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 } 91 157 }