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