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