PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.2.1
WP 2FA – Two-factor authentication for WordPress v2.2.1
4.1.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 4 years ago class-date-time-utils.php 4 years ago class-debugging.php 4 years ago class-generate-modal.php 4 years ago class-migration.php 4 years ago class-request-utils.php 4 years ago class-settings-utils.php 4 years ago class-user-utils.php 4 years ago index.php 5 years ago
class-user-utils.php
394 lines
1 <?php
2 /**
3 * Responsible for different user's manipulations.
4 *
5 * @package wp2fa
6 * @subpackage user-utils
7 * @copyright 2021 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\User;
17 use WP2FA\Admin\Helpers\User_Helper;
18
19 /**
20 * Utility class for creating modal popup markup.
21 *
22 * @package WP2FA\Utils
23 * @since 1.4.2
24 */
25 class User_Utils {
26
27 /**
28 * Holds map with human readable 2FA statuses
29 *
30 * @var array
31 */
32 private static $statuses;
33
34 /**
35 * Determines the proper 2FA status of the given user
36 *
37 * @param [type] $user - The user to check.
38 *
39 * @return array
40 *
41 * @since 2.2.0
42 */
43 public static function determine_user_2fa_status( $user ) {
44
45 // Get current user, we going to need this regardless.
46 $current_user = wp_get_current_user();
47
48 // Bail if we still dont have an object.
49 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
50 return array();
51 }
52
53 $roles = (array) $user->roles;
54
55 // Grab grace period UNIX time.
56 $grace_period_expired = User_Helper::get_grace_period( $user );
57 $is_user_excluded = User::is_excluded( $user->ID );
58 $is_user_enforced = User::is_enforced( $user->ID );
59 $is_user_locked = User_Helper::is_user_locked( $user->ID );
60 $user_last_login = get_user_meta( $user->ID, WP_2FA_PREFIX . 'login_date', true );
61
62 // First lets see if the user already has a token.
63 $enabled_methods = User_Helper::get_enabled_method_for_user( $user );
64
65 $no_enforced_methods = false;
66 if ( 'do-not-enforce' === WP2FA::get_wp2fa_setting( 'enforcement-policy' ) ) {
67 $no_enforced_methods = true;
68 }
69
70 $user_type = array();
71
72 if ( empty( $roles ) ) {
73 $user_type[] = 'orphan_user'; // User has no role.
74 }
75
76 if ( current_user_can( 'manage_options' ) ) {
77 $user_type[] = 'can_manage_options';
78 }
79
80 if ( current_user_can( 'read' ) ) {
81 $user_type[] = 'can_read';
82 }
83
84 if ( $grace_period_expired ) {
85 $user_type[] = 'grace_has_expired';
86 }
87
88 if ( $current_user->ID === $user->ID ) {
89 $user_type[] = 'viewing_own_profile';
90 }
91
92 if ( ! empty( $enabled_methods ) ) {
93 $user_type[] = 'has_enabled_methods';
94 }
95
96 if ( $no_enforced_methods && ! empty( $enabled_methods ) ) {
97 $user_type[] = 'no_required_has_enabled';
98 }
99
100 if ( $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded ) {
101 if ( empty( $user_last_login ) ) {
102 $user_type[] = 'no_determined_yet';
103 } else {
104 $user_type[] = 'no_required_not_enabled';
105 }
106 }
107
108 if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && $is_user_enforced ) {
109 $user_type[] = 'user_needs_to_setup_2fa';
110 }
111
112 if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && ! $is_user_enforced ) {
113 if ( empty( $user_last_login ) ) {
114 $user_type[] = 'no_determined_yet';
115 } else {
116 $user_type[] = 'no_required_not_enabled';
117 }
118 }
119
120 if ( $is_user_excluded ) {
121 $user_type[] = 'user_is_excluded';
122 }
123
124 if ( $is_user_locked ) {
125 $user_type[] = 'user_is_locked';
126 }
127
128 $codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
129 if ( 0 === $codes_remaining ) {
130 $user_type[] = 'user_needs_to_setup_backup_codes';
131 }
132
133 /**
134 * Gives the ability to alter the user types for the user.
135 *
136 * @param string $user_type - Type of the user.
137 * @param \WP_User $user - The WP user.
138 *
139 * @since 2.0.0
140 */
141 return apply_filters( WP_2FA_PREFIX . 'additional_user_types', $user_type, $user );
142 }
143
144 /**
145 * Checks is all values exist in given array
146 *
147 * @param array $needles - Which values to check.
148 * @param array $haystack - The array to check against.
149 *
150 * @return bool
151 *
152 * @since 2.2.0
153 */
154 public static function in_array_all( $needles, $haystack ) {
155 return empty( array_diff( $needles, $haystack ) );
156 }
157
158 /**
159 * Check if role is not in given array of roles
160 *
161 * @param array $roles - All roles.
162 * @param array $user_roles - The User roles.
163 *
164 * @return bool
165 */
166 public static function role_is_not( $roles, $user_roles ) {
167 if (
168 empty(
169 array_intersect(
170 $roles,
171 $user_roles
172 )
173 )
174 ) {
175 return true;
176 }
177
178 return false;
179 }
180
181 /**
182 * Return all users, either by using a direct query or get_users.
183 *
184 * @param string $method Method to use.
185 * @param array $users_args Query arguments.
186 *
187 * @return mixed Array of IDs/Object of Users.
188 */
189 public static function get_all_users_data( $method, $users_args ) {
190
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 );
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
266 global $wpdb;
267
268 $batch_size = isset( $users_args['batch_size'] ) ? $users_args['batch_size'] : false;
269 $offset = isset( $users_args['count'] ) ? $users_args['count'] * $batch_size : false;
270
271 $select = '
272 SELECT ID FROM ' . $wpdb->users . '
273 INNER JOIN ' . $wpdb->usermeta . ' ON ' . $wpdb->users . '.ID = ' . $wpdb->usermeta . '.user_id
274 WHERE ' . $wpdb->usermeta . '.meta_key LIKE \'wp_2fa_%\'
275 ';
276
277 if ( $batch_size ) {
278 $select .= '
279 LIMIT ' . $batch_size . ' OFFSET ' . $offset . '
280 ';
281 }
282
283 $users = $wpdb->get_results( $select );
284
285 $users = array_map(
286 function ( $user ) {
287 return (int) $user->ID;
288 },
289 $users
290 );
291
292 $users = implode( ',', $users );
293
294 return $users;
295 }
296
297 /**
298 * Retrieve string of comma separated IDs.
299 *
300 * @param string $method Method to use.
301 * @param array $users_args Query arguments.
302 *
303 * @return string List of IDs.
304 */
305 public static function get_all_user_ids( $method, $users_args ) {
306 $user_data = self::get_all_users_data( $method, $users_args );
307
308 $users = array_map(
309 function ( $user ) {
310 return (int) $user->ID;
311 },
312 $user_data
313 );
314
315 return implode( ',', $users );
316 }
317
318 /**
319 * Retrieve array if user IDs and login names.
320 *
321 * @param string $method Method to use.
322 * @param array $users_args Query arguments.
323 *
324 * @return array User details.
325 */
326 public static function get_all_user_ids_and_login_names( $method, $users_args ) {
327 $user_data = self::get_all_users_data( $method, $users_args );
328 $user_item = array();
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 'no_determined_yet' => __( '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( $user_types ) {
378 if ( null === self::$statuses ) {
379 self::get_human_readable_user_statuses();
380 }
381
382 foreach ( self::$statuses as $key => $value ) {
383 if ( in_array( $key, $user_types, true ) ) {
384 return array(
385 'id' => $key,
386 'label' => $value,
387 );
388 }
389 }
390
391 return array();
392 }
393 }
394