Ticket #46595: 46595.3.patch
File 46595.3.patch, 10.8 KB (added by , 6 years ago) |
---|
-
src/wp-includes/class-wp-recovery-mode-key-service.php
20 20 * 21 21 * @global PasswordHash $wp_hasher 22 22 * 23 * @return string Recovery mode key.23 * @return array Recovery mode key and Token. 24 24 */ 25 25 public function generate_and_store_recovery_mode_key() { 26 26 … … 44 44 45 45 $hashed = $wp_hasher->HashPassword( $key ); 46 46 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_create_nonce( 'recover_mode_token' ); 55 56 $records[ $token ] = array( 57 'hashed_key' => $hashed, 58 'created_at' => time(), 53 59 ); 54 60 55 return $key; 61 update_option( 'recovery_key', $records ); 62 63 return array( $key, $token ); 56 64 } 57 65 58 66 /** … … 62 70 * 63 71 * @param string $key The unhashed key. 64 72 * @param int $ttl Time in seconds for the key to be valid for. 73 * 65 74 * @return true|WP_Error True on success, error object on failure. 66 75 */ 67 public function validate_recovery_mode_key( $key, $t tl ) {76 public function validate_recovery_mode_key( $key, $token, $ttl ) { 68 77 69 $record = get_option( 'recovery_key' );78 $records = get_option( 'recovery_key' ); 70 79 71 if ( ! $record ) {80 if ( ! $records ) { 72 81 return new WP_Error( 'no_recovery_key_set', __( 'Recovery Mode not initialized.' ) ); 73 82 } 74 83 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 ); 92 93 // as we use nonce to create token lets check it 94 if ( ! wp_verify_nonce( $token, 'recover_mode_token' ) ) { 95 return new WP_Error( 'invalid_token', __( 'Token Invalid/expired.' ) ); 96 } 97 75 98 if ( ! is_array( $record ) || ! isset( $record['hashed_key'], $record['created_at'] ) ) { 76 99 return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) ); 77 100 } … … 86 109 87 110 return true; 88 111 } 112 113 /** 114 * Removes old and used recovery keys. 115 * 116 * @since 5.2.0 117 * 118 * 119 * @param null $token 120 * 121 * @return null. 122 */ 123 public function clean_recovery_key_options( $token = null ){ 124 125 $records = get_option( 'recovery_key' ); 126 127 if( null !== $token ){ 128 129 unset( $records[ $token ] ); 130 } 131 132 foreach ( $records as $key => $record ){ 133 if ( ! wp_verify_nonce( $key, 'recover_mode_token' ) ) { 134 unset( $records[ $key ] ); 135 } 136 } 137 138 update_option( 'recovery_key', $records ); 139 } 89 140 } -
src/wp-includes/class-wp-recovery-mode-link-service.php
12 12 * @since 5.2.0 13 13 */ 14 14 class WP_Recovery_Mode_Link_Service { 15 const LOGIN_ACTION_ENTER 15 const LOGIN_ACTION_ENTER = 'enter_recovery_mode'; 16 16 const LOGIN_ACTION_ENTERED = 'entered_recovery_mode'; 17 17 18 18 /** … … 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_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 * 100 101 * @return string Recovery mode begin URL. 101 102 */ 102 private function get_recovery_mode_begin_url( $key ) {103 private function get_recovery_mode_begin_url( $key, $token ) { 103 104 104 105 $url = add_query_arg( 105 106 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, 108 110 ), 109 111 wp_login_url() 110 112 ); -
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_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_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_recovery_mode_key( 'abcd', 'token', HOUR_IN_SECONDS ); 49 50 $this->assertWPError( $error ); 51 $this->assertEquals( 'invalid_token', $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_create_nonce( 'recover_mode_token' ); 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_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_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_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_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_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_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 update_option( 'recovery_key', array( 'token' => 'gibberish' ) ); 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( 'no_recovery_key_set', $error->get_error_code() ); 156 } 91 157 }