PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.5.0
WP 2FA – Two-factor authentication for WordPress v2.5.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
397 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 namespace WP2FA\Utils;
15
16 use WP2FA\WP2FA;
17 use WP2FA\Methods\Backup_Codes;
18 use WP2FA\Admin\Helpers\User_Helper;
19
20 if ( ! class_exists( '\WP2FA\Utils\User_Utils' ) ) {
21 /**
22 * Utility class for creating modal popup markup.
23 *
24 * @package WP2FA\Utils
25 *
26 * @since 1.4.2
27 */
28 class User_Utils {
29 /**
30 * Holds map with human readable 2FA statuses.
31 *
32 * @var array
33 */
34 private static $statuses;
35
36 /**
37 * Determines the proper 2FA status of the given user.
38 *
39 * @param \WP_User $user - The user to check.
40 *
41 * @return array
42 *
43 * @since 2.2.0
44 */
45 public static function determine_user_2fa_status( $user ) {
46 // Get current user, we going to need this regardless.
47 $current_user = wp_get_current_user();
48
49 // Bail if we still dont have an object.
50 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
51 return array();
52 }
53
54 $roles = (array) $user->roles;
55
56 // Grab grace period UNIX time.
57 $grace_period_expired = User_Helper::get_grace_period( $user );
58 $is_user_excluded = User_Helper::is_excluded( $user->ID );
59 $is_user_enforced = User_Helper::is_enforced( $user->ID );
60 $is_user_locked = User_Helper::is_user_locked( $user->ID );
61 $user_last_login = User_Helper::get_login_date_for_user( $user->ID );
62
63 // First lets see if the user already has a token.
64 $enabled_methods = User_Helper::get_enabled_method_for_user( $user );
65
66 $no_enforced_methods = false;
67 if ( 'do-not-enforce' === WP2FA::get_wp2fa_setting( 'enforcement-policy' ) ) {
68 $no_enforced_methods = true;
69 }
70
71 $user_type = array();
72 // Order is important here - for speed optimizations see self::extract_statuses() function of that class - we probably need to redo the whole thing.
73 if ( $no_enforced_methods && ! empty( $enabled_methods ) ) {
74 $user_type[] = 'no_required_has_enabled';
75 }
76
77 if ( $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded ) {
78 if ( empty( $user_last_login ) ) {
79 $user_type[] = User_Helper::USER_UNDETERMINED_STATUS;
80 } else {
81 $user_type[] = 'no_required_not_enabled';
82 }
83 }
84
85 if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && $is_user_enforced ) {
86 $user_type[] = 'user_needs_to_setup_2fa';
87 }
88
89 if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && ! $is_user_enforced ) {
90 if ( empty( $user_last_login ) ) {
91 $user_type[] = User_Helper::USER_UNDETERMINED_STATUS;
92 } else {
93 $user_type[] = 'no_required_not_enabled';
94 }
95 }
96
97 if ( $is_user_excluded ) {
98 $user_type[] = 'user_is_excluded';
99 }
100
101 if ( $is_user_locked ) {
102 $user_type[] = 'user_is_locked';
103 }
104
105 if ( ! empty( $enabled_methods ) ) {
106 $user_type[] = 'has_enabled_methods';
107 }
108
109 $codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
110 if ( 0 === $codes_remaining ) {
111 $user_type[] = 'user_needs_to_setup_backup_codes';
112 }
113
114 if ( empty( $roles ) ) {
115 $user_type[] = 'orphan_user'; // User has no role.
116 }
117
118 if ( current_user_can( 'manage_options' ) ) {
119 $user_type[] = 'can_manage_options';
120 }
121
122 if ( current_user_can( 'read' ) ) {
123 $user_type[] = 'can_read';
124 }
125
126 if ( $grace_period_expired ) {
127 $user_type[] = 'grace_has_expired';
128 }
129
130 if ( $current_user->ID === $user->ID ) {
131 $user_type[] = 'viewing_own_profile';
132 }
133
134 /*
135 * Gives the ability to alter the user types for the user.
136 *
137 * @param string $user_type - Type of the user.
138 * @param \WP_User $user - The WP user.
139 *
140 * @since 2.0.0
141 */
142 return apply_filters( WP_2FA_PREFIX . 'additional_user_types', $user_type, $user );
143 }
144
145 /**
146 * Checks is all values exist in given array.
147 *
148 * @param array $needles - Which values to check.
149 * @param array $haystack - The array to check against.
150 *
151 * @return bool
152 *
153 * @since 2.2.0
154 */
155 public static function in_array_all( $needles, $haystack ) {
156 return empty( array_diff( $needles, $haystack ) );
157 }
158
159 /**
160 * Check if role is not in given array of roles.
161 *
162 * @param array $roles - All roles.
163 * @param array $user_roles - The User roles.
164 *
165 * @return bool
166 */
167 public static function role_is_not( $roles, $user_roles ) {
168 if (
169 empty(
170 array_intersect(
171 $roles,
172 $user_roles
173 )
174 )
175 ) {
176 return true;
177 }
178
179 return false;
180 }
181
182 /**
183 * Return all users, either by using a direct query or get_users.
184 *
185 * @param string $method Method to use.
186 * @param array $users_args Query arguments.
187 *
188 * @return mixed Array of IDs/Object of Users.
189 */
190 public static function get_all_users_data( $method, $users_args ) {
191 if ( 'get_users' === $method ) {
192 return get_users( $users_args );
193 }
194
195 // method is "query", let's build the SQL query ourselves.
196 global $wpdb;
197
198 $batch_size = isset( $users_args['batch_size'] ) ? $users_args['batch_size'] : false;
199 $offset = isset( $users_args['count'] ) ? $users_args['count'] * $batch_size : false;
200
201 // Default.
202 $select = 'SELECT ID, user_login FROM ' . $wpdb->users . '';
203
204 // If we want to grab users with a specific role.
205 if ( isset( $users_args['role__in'] ) && ! empty( $users_args['role__in'] ) ) {
206 $roles = $users_args['role__in'];
207 $select = '
208 SELECT ID, user_login
209 FROM ' . $wpdb->users . ' u INNER JOIN ' . $wpdb->usermeta . ' um
210 ON u.ID = um.user_id
211 WHERE um.meta_key LIKE \'' . $wpdb->base_prefix . '%capabilities\'' . // phpcs:ignore
212 ' AND (
213 ';
214 $i = 1;
215 foreach ( $roles as $role ) {
216 $select .= ' um.meta_value LIKE \'%"' . $role . '"%\' ';
217 if ( $i < count( $roles ) ) {
218 $select .= ' OR ';
219 }
220 ++$i;
221 }
222 $select .= ' ) ';
223
224 $excluded_users = ( ! empty( $users_args['excluded_users'] ) ) ? $users_args['excluded_users'] : array();
225
226 $excluded_users = array_map(
227 function ( $excluded_user ) {
228 return '"' . $excluded_user . '"';
229 },
230 $excluded_users
231 );
232
233 if ( ! empty( $excluded_users ) ) {
234 $select .= '
235 AND user_login NOT IN ( ' . implode( ',', $excluded_users ) . ' )
236 ';
237 }
238
239 $skip_existing_2fa_users = ( ! empty( $users_args['skip_existing_2fa_users'] ) ) ? $users_args['skip_existing_2fa_users'] : false;
240
241 if ( $skip_existing_2fa_users ) {
242 $select .= '
243 AND u.ID NOT IN (
244 SELECT DISTINCT user_id FROM ' . $wpdb->usermeta . ' WHERE meta_key = \'wp_2fa_enabled_methods\'
245 )
246 ';
247 }
248 }
249
250 if ( $batch_size ) {
251 $select .= ' LIMIT ' . $batch_size . ' OFFSET ' . $offset . '';
252 }
253
254 return $wpdb->get_results( $select ); // phpcs:ignore
255 }
256
257 /**
258 * Collects all the users with 2FA meta data.
259 *
260 * @param array $users_args - Arguments.
261 *
262 * @return string
263 */
264 public static function get_all_user_ids_who_have_wp_2fa_metadata_present( $users_args ) {
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( array $user_types ) {
376 if ( null === self::$statuses ) {
377 self::get_human_readable_user_statuses();
378 }
379
380 if ( empty( $user_types ) ) {
381 return array();
382 }
383
384 $key_to_search = reset( $user_types );
385
386 if ( isset( self::$statuses[ $key_to_search ] ) ) {
387 return array(
388 'id' => $key_to_search,
389 'label' => self::$statuses[ $key_to_search ],
390 );
391 }
392
393 return array();
394 }
395 }
396 }
397