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