PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.8.0
WP 2FA – Two-factor authentication for WordPress v2.8.0
4.0.0 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 / Utils / class-user-utils.php
wp-2fa / includes / classes / Utils Last commit date
class-abstract-migration.php 1 year ago class-date-time-utils.php 1 year ago class-debugging.php 1 year ago class-generate-modal.php 1 year ago class-migration.php 1 year ago class-request-utils.php 1 year ago class-settings-utils.php 1 year ago class-user-utils.php 1 year ago class-white-label.php 1 year ago index.php 1 year ago
class-user-utils.php
411 lines
1 <?php
2 /**
3 * Responsible for different user's manipulations.
4 *
5 * @package wp2fa
6 * @subpackage user-utils
7 *
8 * @copyright 2024 Melapress
9 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
10 *
11 * @see https://wordpress.org/plugins/wp-2fa/
12 */
13
14 declare(strict_types=1);
15
16 namespace WP2FA\Utils;
17
18 use WP2FA\WP2FA;
19 use WP2FA\Methods\Backup_Codes;
20 use WP2FA\Admin\Helpers\User_Helper;
21
22 if ( ! class_exists( '\WP2FA\Utils\User_Utils' ) ) {
23 /**
24 * Utility class for creating modal popup markup.
25 *
26 * @package WP2FA\Utils
27 *
28 * @since 1.4.2
29 */
30 class User_Utils {
31 /**
32 * Holds map with human readable 2FA statuses.
33 *
34 * @var array
35 *
36 * @since 2.2.0
37 */
38 private static $statuses;
39
40 /**
41 * Determines the proper 2FA status of the given user.
42 *
43 * @param \WP_User $user - The user to check.
44 *
45 * @return array
46 *
47 * @since 2.2.0
48 */
49 public static function determine_user_2fa_status( $user ) {
50 // Get current user, we going to need this regardless.
51 $current_user = wp_get_current_user();
52
53 // Bail if we still dont have an object.
54 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
55 return array();
56 }
57
58 $roles = (array) $user->roles;
59
60 // Grab grace period UNIX time.
61 $grace_period_expired = User_Helper::get_grace_period( $user );
62 $is_user_excluded = User_Helper::is_excluded( $user->ID );
63 $is_user_enforced = User_Helper::is_enforced( $user->ID );
64 $is_user_locked = User_Helper::is_user_locked( $user->ID );
65 $user_last_login = User_Helper::get_login_date_for_user( $user->ID );
66
67 // First lets see if the user already has a token.
68 $enabled_methods = User_Helper::get_enabled_method_for_user( $user );
69
70 $no_enforced_methods = false;
71 if ( 'do-not-enforce' === WP2FA::get_wp2fa_setting( 'enforcement-policy' ) ) {
72 $no_enforced_methods = true;
73 }
74
75 $user_type = array();
76 // Order is important here - for speed optimizations see self::extract_statuses() function of that class - we probably need to redo the whole thing.
77 if ( $no_enforced_methods && ! empty( $enabled_methods ) ) {
78 $user_type[] = 'no_required_has_enabled';
79 }
80
81 if ( $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded ) {
82 if ( empty( $user_last_login ) ) {
83 $user_type[] = User_Helper::USER_UNDETERMINED_STATUS;
84 } else {
85 $user_type[] = 'no_required_not_enabled';
86 }
87 }
88
89 if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && $is_user_enforced ) {
90 $user_type[] = 'user_needs_to_setup_2fa';
91 }
92
93 if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && ! $is_user_enforced ) {
94 if ( empty( $user_last_login ) ) {
95 $user_type[] = User_Helper::USER_UNDETERMINED_STATUS;
96 } else {
97 $user_type[] = 'no_required_not_enabled';
98 }
99 }
100
101 if ( $is_user_excluded ) {
102 $user_type[] = 'user_is_excluded';
103 }
104
105 if ( $is_user_locked ) {
106 $user_type[] = 'user_is_locked';
107 }
108
109 if ( ! empty( $enabled_methods ) ) {
110 $user_type[] = 'has_enabled_methods';
111 }
112
113 $codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
114 if ( 0 === $codes_remaining ) {
115 $user_type[] = 'user_needs_to_setup_backup_codes';
116 }
117
118 if ( empty( $roles ) ) {
119 $user_type[] = 'orphan_user'; // User has no role.
120 }
121
122 if ( \current_user_can( 'manage_options' ) ) {
123 $user_type[] = 'can_manage_options';
124 }
125
126 if ( \current_user_can( 'read' ) ) {
127 $user_type[] = 'can_read';
128 }
129
130 if ( $grace_period_expired ) {
131 $user_type[] = 'grace_has_expired';
132 }
133
134 if ( $current_user->ID === $user->ID ) {
135 $user_type[] = 'viewing_own_profile';
136 }
137
138 /*
139 * Gives the ability to alter the user types for the user.
140 *
141 * @param string $user_type - Type of the user.
142 * @param \WP_User $user - The WP user.
143 *
144 * @since 2.0.0
145 */
146 return \apply_filters( WP_2FA_PREFIX . 'additional_user_types', $user_type, $user );
147 }
148
149 /**
150 * Checks is all values exist in given array.
151 *
152 * @param array $needles - Which values to check.
153 * @param array $haystack - The array to check against.
154 *
155 * @return bool
156 *
157 * @since 2.2.0
158 */
159 public static function in_array_all( $needles, $haystack ) {
160 return empty( array_diff( $needles, $haystack ) );
161 }
162
163 /**
164 * Check if role is not in given array of roles.
165 *
166 * @param array $roles - All roles.
167 * @param array $user_roles - The User roles.
168 *
169 * @return bool
170 *
171 * @since 2.2.0
172 */
173 public static function role_is_not( $roles, $user_roles ) {
174 if (
175 empty(
176 array_intersect(
177 $roles,
178 $user_roles
179 )
180 )
181 ) {
182 return true;
183 }
184
185 return false;
186 }
187
188 /**
189 * Return all users, either by using a direct query or get_users.
190 *
191 * @param string $method Method to use.
192 * @param array $users_args Query arguments.
193 *
194 * @return mixed Array of IDs/Object of Users.
195 *
196 * @since 2.2.0
197 */
198 public static function get_all_users_data( $method, $users_args ) {
199 if ( 'get_users' === $method ) {
200 return get_users( $users_args );
201 }
202
203 // method is "query", let's build the SQL query ourselves.
204 global $wpdb;
205
206 $batch_size = isset( $users_args['batch_size'] ) ? $users_args['batch_size'] : false;
207 $offset = isset( $users_args['count'] ) ? $users_args['count'] * $batch_size : false;
208
209 // Default.
210 $select = 'SELECT ID, user_login FROM ' . $wpdb->users . '';
211
212 // If we want to grab users with a specific role.
213 if ( isset( $users_args['role__in'] ) && ! empty( $users_args['role__in'] ) ) {
214 $roles = $users_args['role__in'];
215 $select = '
216 SELECT ID, user_login
217 FROM ' . $wpdb->users . ' u INNER JOIN ' . $wpdb->usermeta . ' um
218 ON u.ID = um.user_id
219 WHERE um.meta_key LIKE \'' . $wpdb->base_prefix . '%capabilities\'' . // phpcs:ignore
220 ' AND (
221 ';
222 $i = 1;
223 foreach ( $roles as $role ) {
224 $select .= ' um.meta_value LIKE \'%"' . $role . '"%\' ';
225 if ( $i < count( $roles ) ) {
226 $select .= ' OR ';
227 }
228 ++$i;
229 }
230 $select .= ' ) ';
231
232 $excluded_users = ( ! empty( $users_args['excluded_users'] ) ) ? $users_args['excluded_users'] : array();
233
234 $excluded_users = array_map(
235 function ( $excluded_user ) {
236 return '"' . $excluded_user . '"';
237 },
238 $excluded_users
239 );
240
241 if ( ! empty( $excluded_users ) ) {
242 $select .= '
243 AND user_login NOT IN ( ' . implode( ',', $excluded_users ) . ' )
244 ';
245 }
246
247 $skip_existing_2fa_users = ( ! empty( $users_args['skip_existing_2fa_users'] ) ) ? $users_args['skip_existing_2fa_users'] : false;
248
249 if ( $skip_existing_2fa_users ) {
250 $select .= '
251 AND u.ID NOT IN (
252 SELECT DISTINCT user_id FROM ' . $wpdb->usermeta . ' WHERE meta_key = \'wp_2fa_enabled_methods\'
253 )
254 ';
255 }
256 }
257
258 if ( $batch_size ) {
259 $select .= ' LIMIT ' . $batch_size . ' OFFSET ' . $offset . '';
260 }
261
262 return $wpdb->get_results( $select ); // phpcs:ignore
263 }
264
265 /**
266 * Collects all the users with 2FA meta data.
267 *
268 * @param array $users_args - Arguments.
269 *
270 * @return string
271 *
272 * @since 2.2.0
273 */
274 public static function get_all_user_ids_who_have_wp_2fa_metadata_present( $users_args ) {
275 global $wpdb;
276
277 $batch_size = isset( $users_args['batch_size'] ) ? $users_args['batch_size'] : false;
278 $offset = isset( $users_args['count'] ) ? $users_args['count'] * $batch_size : false;
279
280 $select = '
281 SELECT ID FROM ' . $wpdb->users . '
282 INNER JOIN ' . $wpdb->usermeta . ' ON ' . $wpdb->users . '.ID = ' . $wpdb->usermeta . '.user_id
283 WHERE ' . $wpdb->usermeta . '.meta_key LIKE \'wp_2fa_%\'
284 ';
285
286 if ( $batch_size ) {
287 $select .= '
288 LIMIT ' . $batch_size . ' OFFSET ' . $offset . '
289 ';
290 }
291
292 $users = $wpdb->get_results( $select ); // phpcs:ignore
293
294 $users = array_map(
295 function ( $user ) {
296 return (int) $user->ID;
297 },
298 $users
299 );
300
301 $users = implode( ',', $users );
302
303 return $users;
304 }
305
306 /**
307 * Retrieve string of comma separated IDs.
308 *
309 * @param string $method Method to use.
310 * @param array $users_args Query arguments.
311 *
312 * @return string List of IDs.
313 *
314 * @since 2.2.0
315 */
316 public static function get_all_user_ids( $method, $users_args ) {
317 $user_data = self::get_all_users_data( $method, $users_args );
318
319 $users = array_map(
320 function ( $user ) {
321 return (int) $user->ID;
322 },
323 $user_data
324 );
325
326 return implode( ',', $users );
327 }
328
329 /**
330 * Retrieve array if user IDs and login names.
331 *
332 * @param string $method Method to use.
333 * @param array $users_args Query arguments.
334 *
335 * @return array User details.
336 *
337 * @since 2.2.0
338 */
339 public static function get_all_user_ids_and_login_names( $method, $users_args ) {
340 $user_data = self::get_all_users_data( $method, $users_args );
341
342 $users = array_map(
343 function ( $user ) {
344 $user_item['ID'] = (int) $user->ID;
345 $user_item['user_login'] = $user->user_login;
346
347 return $user_item;
348 },
349 $user_data
350 );
351
352 return $users;
353 }
354
355 /**
356 * Returns the array with human readable statuses of the WP 2FA.
357 *
358 * @since 1.6
359 *
360 * @return array
361 */
362 public static function get_human_readable_user_statuses() {
363 if ( null === self::$statuses ) {
364 self::$statuses =
365 array(
366 'has_enabled_methods' => __( 'Configured', 'wp-2fa' ),
367 'user_needs_to_setup_2fa' => __( 'Required but not configured', 'wp-2fa' ),
368 'no_required_has_enabled' => __( 'Configured (but not required)', 'wp-2fa' ),
369 'no_required_not_enabled' => __( 'Not required & not configured', 'wp-2fa' ),
370 'user_is_excluded' => __( 'Not allowed', 'wp-2fa' ),
371 'user_is_locked' => __( 'Locked', 'wp-2fa' ),
372 User_Helper::USER_UNDETERMINED_STATUS => __( 'User has not logged in yet, 2FA status is unknown', 'wp-2fa' ),
373 );
374 }
375
376 return self::$statuses;
377 }
378
379 /**
380 * Gets the user types extracted with @see User_Utils::determine_user_2fa_status,
381 * checks values and generates human readable 2FA status text.
382 *
383 * @param array $user_types - The types of the user.
384 *
385 * @return array An array with the id and label elements of user 2FA status. Empty in case there is not match.
386 *
387 * @since 1.7.0 Changed the function to return the id and label of the first match it finds instead of concatenated labels of all matched statuses.
388 */
389 public static function extract_statuses( array $user_types ) {
390 if ( null === self::$statuses ) {
391 self::get_human_readable_user_statuses();
392 }
393
394 if ( empty( $user_types ) ) {
395 return array();
396 }
397
398 $key_to_search = reset( $user_types );
399
400 if ( isset( self::$statuses[ $key_to_search ] ) ) {
401 return array(
402 'id' => $key_to_search,
403 'label' => self::$statuses[ $key_to_search ],
404 );
405 }
406
407 return array();
408 }
409 }
410 }
411