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