class-wp-recovery-mode-key-service.php
4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<?php
/**
* Error Protection API: WP_Recovery_Mode_Key_Service class
*
* @package WordPress
* @since 5.2.0
*/
/**
* Core class used to generate and validate keys used to enter Recovery Mode.
*
* @since 5.2.0
*/
final class WP_Recovery_Mode_Key_Service {
/**
* The option name used to store the keys.
*
* @since 5.2.0
* @var string
*/
private $option_name = 'recovery_keys';
/**
* Creates a recovery mode token.
*
* @since 5.2.0
*
* @return string A random string to identify its associated key in storage.
*/
public function generate_recovery_mode_token() {
return wp_generate_password( 22, false );
}
/**
* Creates a recovery mode key.
*
* @since 5.2.0
*
* @global PasswordHash $wp_hasher
*
* @param string $token A token generated by {@see generate_recovery_mode_token()}.
* @return string Recovery mode key.
*/
public function generate_and_store_recovery_mode_key( $token ) {
global $wp_hasher;
$key = wp_generate_password( 22, false );
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . WPINC . '/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
$hashed = $wp_hasher->HashPassword( $key );
$records = $this->get_keys();
$records[ $token ] = array(
'hashed_key' => $hashed,
'created_at' => time(),
);
$this->update_keys( $records );
/**
* Fires when a recovery mode key is generated.
*
* @since 5.2.0
*
* @param string $token The recovery data token.
* @param string $key The recovery mode key.
*/
do_action( 'generate_recovery_mode_key', $token, $key );
return $key;
}
/**
* Verifies if the recovery mode key is correct.
*
* Recovery mode keys can only be used once; the key will be consumed in the process.
*
* @since 5.2.0
*
* @param string $token The token used when generating the given key.
* @param string $key The unhashed key.
* @param int $ttl Time in seconds for the key to be valid for.
* @return true|WP_Error True on success, error object on failure.
*/
public function validate_recovery_mode_key( $token, $key, $ttl ) {
$records = $this->get_keys();
if ( ! isset( $records[ $token ] ) ) {
return new WP_Error( 'token_not_found', __( 'Recovery Mode not initialized.' ) );
}
$record = $records[ $token ];
$this->remove_key( $token );
if ( ! is_array( $record ) || ! isset( $record['hashed_key'], $record['created_at'] ) ) {
return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) );
}
if ( ! wp_check_password( $key, $record['hashed_key'] ) ) {
return new WP_Error( 'hash_mismatch', __( 'Invalid recovery key.' ) );
}
if ( time() > $record['created_at'] + $ttl ) {
return new WP_Error( 'key_expired', __( 'Recovery key expired.' ) );
}
return true;
}
/**
* Removes expired recovery mode keys.
*
* @since 5.2.0
*
* @param int $ttl Time in seconds for the keys to be valid for.
*/
public function clean_expired_keys( $ttl ) {
$records = $this->get_keys();
foreach ( $records as $key => $record ) {
if ( ! isset( $record['created_at'] ) || time() > $record['created_at'] + $ttl ) {
unset( $records[ $key ] );
}
}
$this->update_keys( $records );
}
/**
* Removes a used recovery key.
*
* @since 5.2.0
*
* @param string $token The token used when generating a recovery mode key.
*/
private function remove_key( $token ) {
$records = $this->get_keys();
if ( ! isset( $records[ $token ] ) ) {
return;
}
unset( $records[ $token ] );
$this->update_keys( $records );
}
/**
* Gets the recovery key records.
*
* @since 5.2.0
*
* @return array Associative array of $token => $data pairs, where $data has keys 'hashed_key'
* and 'created_at'.
*/
private function get_keys() {
return (array) get_option( $this->option_name, array() );
}
/**
* Updates the recovery key records.
*
* @since 5.2.0
*
* @param array $keys Associative array of $token => $data pairs, where $data has keys 'hashed_key'
* and 'created_at'.
* @return bool True on success, false on failure.
*/
private function update_keys( array $keys ) {
return update_option( $this->option_name, $keys );
}
}