PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.4.2
WP 2FA – Two-factor authentication for WordPress v2.4.2
1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Authenticator / class-backup-codes.php
wp-2fa / includes / classes / Authenticator Last commit date
class-authentication.php 3 years ago class-backup-codes.php 3 years ago class-backupcodes.php 3 years ago class-login.php 2 years ago class-open-ssl.php 3 years ago index.php 5 years ago
class-backup-codes.php
292 lines
1 <?php
2 /**
3 * Responsible for WP2FA user's backup codes manipulation.
4 *
5 * @package wp2fa
6 * @subpackage backup-codes
7 * @copyright 2023 WP White Security
8 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
9 * @link https://wordpress.org/plugins/wp-2fa/
10 */
11
12 /**
13 * Class for handling backup codes
14 *
15 * @since 0.1-dev
16 *
17 * @package WP2FA
18 */
19
20 namespace WP2FA\Authenticator;
21
22 use WP2FA\Admin\Settings_Page;
23 use WP2FA\Admin\Helpers\User_Helper;
24 use WP2FA\Admin\Controllers\Login_Attempts;
25 use \WP2FA\Authenticator\Authentication as Authentication;
26
27 /**
28 * Backup code class, for handling backup code generation and such.
29 */
30 class Backup_Codes {
31
32 /**
33 * Holds the name of the meta key for the allowed login attempts
34 *
35 * @var string
36 *
37 * @since 2.0.0
38 */
39 private static $login_num_meta_key = WP_2FA_PREFIX . 'backup-login-attempts';
40
41 /**
42 * Key used for backup codes
43 *
44 * @var string
45 */
46 const BACKUP_CODES_META_KEY = 'wp_2fa_backup_codes';
47
48 /**
49 * The number backup codes.
50 *
51 * @type int
52 */
53 const NUMBER_OF_CODES = 10;
54
55 /**
56 * The name of the method
57 *
58 * @var string
59 *
60 * @since 2.0.0
61 */
62 public static $method_name = 'backup_codes';
63
64 /**
65 * The login attempts class
66 *
67 * @var \WP2FA\Admin\Controllers\Login_Attempts
68 *
69 * @since 2.0.0
70 */
71 private static $login_attempts = null;
72
73 /**
74 * Lets build!
75 */
76 public static function init() {
77 \add_filter( WP_2FA_PREFIX . 'backup_methods_list', array( __CLASS__, 'add_backup_method' ), 10, 2 );
78 \add_filter( WP_2FA_PREFIX . 'backup_methods_enabled', array( __CLASS__, 'check_backup_method' ), 10, 2 );
79 \add_action( 'wp_ajax_wp2fa_run_ajax_generate_json', array( __CLASS__, 'run_ajax_generate_json' ) );
80 }
81
82 /**
83 * Generate backup codes
84 *
85 * @param object $user User data.
86 * @param string $args possible args.
87 */
88 public static function generate_codes( $user, $args = '' ) {
89 $codes = array();
90 $codes_hashed = array();
91
92 // Check for arguments.
93 if ( isset( $args['number'] ) ) {
94 $num_codes = (int) $args['number'];
95 } else {
96 $num_codes = self::NUMBER_OF_CODES;
97 }
98
99 // Append or replace (default).
100 if ( isset( $args['method'] ) && 'append' === $args['method'] ) {
101 $codes_hashed = (array) get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
102 }
103
104 for ( $i = 0; $i < $num_codes; $i++ ) {
105 $code = Authentication::get_code();
106 $codes_hashed[] = wp_hash_password( $code );
107 $codes[] = $code;
108 unset( $code );
109 }
110
111 update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $codes_hashed );
112
113 // Unhashed.
114 return $codes;
115 }
116
117 /**
118 * Returns instance of the LoginAttempts class
119 *
120 * @return \WP2FA\Admin\Controllers\Login_Attempts
121 *
122 * @since 2.0.0
123 */
124 public static function get_login_attempts_instance() {
125 if ( null === self::$login_attempts ) {
126
127 self::$login_attempts = new Login_Attempts( self::$login_num_meta_key );
128
129 }
130 return self::$login_attempts;
131 }
132
133 /**
134 * Checks the number of login attempts
135 *
136 * @param \WP_User $user - The user we have to check for.
137 *
138 * @return boolean
139 *
140 * @since 2.0.0
141 */
142 public static function check_number_of_attempts( \WP_User $user ):bool {
143 return self::get_login_attempts_instance()->check_number_of_attempts( $user );
144 }
145
146 /**
147 * Generate codes and check remaining amount for user.
148 */
149 public static function run_ajax_generate_json() {
150 $user = wp_get_current_user();
151
152 check_ajax_referer( 'wp-2fa-backup-codes-generate-json-' . $user->ID, 'nonce' );
153
154 // Setup the return data.
155 $codes = self::generate_codes( $user );
156
157 $count = self::codes_remaining_for_user( $user );
158 $i18n = array(
159 'count' => esc_html(
160 sprintf(
161 /* translators: %s: count */
162 _n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'wp-2fa' ),
163 $count
164 )
165 ),
166 /* translators: %s: the site's domain */
167 'title' => esc_html__( 'Two-Factor Backup Codes for %s', 'wp-2fa' ),
168 );
169
170 // Send the response.
171 wp_send_json_success(
172 array(
173 'codes' => $codes,
174 'i18n' => $i18n,
175 )
176 );
177 }
178
179 /**
180 * Grab number of unused backup codes within the users position.
181 *
182 * @param object $user User data.
183 * @return int Count of codes.
184 */
185 public static function codes_remaining_for_user( $user ) {
186 $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
187 if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) {
188
189 return count( $backup_codes );
190 }
191 return 0;
192 }
193
194 /**
195 * Validate backup codes
196 *
197 * @param object $user User data.
198 * @param string $code The code we are checking.
199 * @return bool Is is valid or not.
200 */
201 public static function validate_code( $user, $code ) {
202 $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
203 if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) {
204 foreach ( $backup_codes as $code_index => $code_hashed ) {
205 if ( wp_check_password( $code, $code_hashed, $user->ID ) ) {
206 self::delete_code( $user, $code_hashed );
207 self::get_login_attempts_instance()->clear_login_attempts( $user );
208
209 return true;
210 }
211 }
212 }
213 self::get_login_attempts_instance()->increase_login_attempts( $user );
214
215 return false;
216 }
217
218 /**
219 * Delete code once its used.
220 *
221 * @param object $user User data.
222 * @param string $code_hashed Code to delete.
223 */
224 public static function delete_code( $user, $code_hashed ) {
225 $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
226
227 // Delete the current code from the list since it's been used.
228 $backup_codes = array_flip( $backup_codes );
229 unset( $backup_codes[ $code_hashed ] );
230 $backup_codes = array_values( array_flip( $backup_codes ) );
231
232 // Update the backup code master list.
233 update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $backup_codes );
234 }
235
236 /**
237 * Add the method to the existing backup methods array
238 *
239 * @param array $backup_methods - Array with the currently supported backup methods.
240 *
241 * @return array
242 *
243 * @since 2.0.0
244 */
245 public static function add_backup_method( array $backup_methods ): array {
246 return array_merge(
247 $backup_methods,
248 array(
249 self::$method_name => array(
250 'wizard-step' => '2fa-wizard-config-backup-codes',
251 'button_name' => sprintf(
252 /* translators: URL with more information about the backup codes */
253 esc_html__( 'Login with a backup code: you will get 10 backup codes and you can use one of them when you need to login and you cannot generate a code from the app. %s', 'wp-2fa' ),
254 '<a href="https://www.wpwhitesecurity.com/2fa-backup-codes/" target="_blank">' . esc_html__( 'More information.', 'wp-2fa' ) . '</a>'
255 ),
256 ),
257 )
258 );
259 }
260
261 /**
262 * Changes the global backup methods array - removes the method if it is not enabled
263 *
264 * @param array $backup_methods - Array with all global backup methods.
265 * @param \WP_User $user - User to check for is that method enabled.
266 *
267 * @return array
268 *
269 * @since 2.0.0
270 */
271 public static function check_backup_method( array $backup_methods, \WP_User $user ): array {
272 $enabled = Settings_Page::are_backup_codes_enabled( User_Helper::get_user_role( $user ) );
273
274 if ( ! $enabled ) {
275 unset( $backup_methods[ self::$method_name ] );
276 }
277
278 return $backup_methods;
279 }
280
281 /**
282 * Returns the name of the method
283 *
284 * @return string
285 *
286 * @since 2.0.0
287 */
288 public static function get_method_name(): string {
289 return self::$method_name;
290 }
291 }
292