PluginProbe ʕ •ᴥ•ʔ
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) / 9.5.9
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) v9.5.9
9.5.11 9.5.10.1 9.5.10 trunk 9.4.0 9.4.1 9.4.2 9.4.3 9.5.0 9.5.0.1 9.5.0.2 9.5.1 9.5.2 9.5.2.2 9.5.2.3 9.5.3 9.5.3.1 9.5.3.2 9.5.4 9.5.5 9.5.6 9.5.7 9.5.8 9.5.9
really-simple-ssl / security / wordpress / two-fa / class-rsssl-two-factor-settings.php
really-simple-ssl / security / wordpress / two-fa Last commit date
assets 2 months ago contracts 2 months ago controllers 2 months ago models 2 months ago providers 2 months ago repositories 2 months ago services 2 months ago traits 2 months ago class-rsssl-parameter-validation.php 2 months ago class-rsssl-passkey-list-table.php 2 months ago class-rsssl-two-fa-authentication.php 2 months ago class-rsssl-two-fa-data-parameters.php 2 months ago class-rsssl-two-fa-status.php 2 months ago class-rsssl-two-factor-admin.php 2 months ago class-rsssl-two-factor-compat.php 2 months ago class-rsssl-two-factor-on-board-api.php 2 months ago class-rsssl-two-factor-profile-settings.php 2 months ago class-rsssl-two-factor-settings.php 2 months ago class-rsssl-two-factor.php 2 months ago function-login-footer.php 2 months ago function-login-header.php 2 months ago
class-rsssl-two-factor-settings.php
873 lines
1 <?php
2 /**
3 * Holds the request parameters for a specific action.
4 * This class holds the request parameters for a specific action.
5 * It is used to store the parameters and pass them to the functions.
6 *
7 * @package REALLY_SIMPLE_SSL
8 */
9
10 namespace RSSSL\Security\WordPress\Two_Fa;
11
12 use RSSSL\Pro\Security\WordPress\Two_Fa\Providers\Rsssl_Two_Factor_Passkey;
13 use RSSSL\Security\WordPress\Two_Fa\Providers\Rsssl_Provider_Loader;
14 use RSSSL\Security\WordPress\Two_Fa\Providers\Rsssl_Two_Factor_Email;
15 use RSSSL\Pro\Security\WordPress\Two_Fa\Providers\Rsssl_Two_Factor_Totp;
16 use WP_User;
17
18 /**
19 * Class Rsssl_Two_Factor_Settings
20 *
21 * This class handles the settings for the Two-Factor Authentication plugin.
22 */
23 class Rsssl_Two_Factor_Settings {
24 /**
25 * The class instance.
26 *
27 * @var Rsssl_Two_Factor_Settings
28 */
29 private static $instance;
30
31 /**
32 * The forced roles for 2FA.
33 *
34 * @var array $forced_roles
35 */
36 public static $forced_roles;
37
38 /**
39 * The enabled roles for TOTP.
40 *
41 * @var $enabled_roles_totp
42 */
43 public static $enabled_roles_totp;
44
45 /**
46 * The forced roles for TOTP dynamically generated by logic.
47 *
48 * @var $forced_roles_totp
49 */
50 public static $forced_roles_totp; // @codingStandardsIgnoreLine It is dynamically generated by logic.
51
52 /**
53 * The enabled roles for Email dynamically generated by logic.
54 *
55 * @var $enabled_roles_totp
56 */
57 public static $forced_roles_email; // @codingStandardsIgnoreLine It is dynamically generated by logic.
58
59 /**
60 * The enabled roles for Email.
61 *
62 * @var array $enabled_roles_email
63 */
64 public static $enabled_roles_email;
65
66 /**
67 * The enabled roles for Passkey, hint they all are.
68 *
69 * @var array $enabled_roles_passkey
70 */
71 public static $enabled_roles_passkey;
72
73 /**
74 * The forced roles for Passkey.
75 *
76 * @var array $forced_roles_passkey
77 */
78 public static $forced_roles_passkey;
79
80 /**
81 * If the previous roles variables are loaded or not.
82 *
83 * @var bool $roles_loaded
84 */
85 private static $roles_loaded = false;
86
87 /**
88 * The user meta enabled providers key.
89 *
90 * @type string
91 */
92 const RSSSL_ENABLED_PROVIDERS_USER_META_KEY = 'rsssl_two_fa_providers';
93
94 /**
95 * Class constructor.
96 *
97 * Checks if the class instance has already been initialized. If so, returns
98 * immediately. Otherwise, assigns the class instance to the static variable
99 * "self::$instance".
100 */
101 public function __construct() {
102 if ( isset( self::$instance ) ) {
103 return;
104 }
105
106 self::$instance = $this;
107 }
108
109
110 /**
111 * Get user roles for a user, cross multisite.
112 *
113 * @param int $user_id //the user id to get the roles for.
114 *
115 * @return array
116 */
117 public static function get_user_roles( int $user_id ): array {
118 if ( is_multisite() ) {
119 $strict_roles = self::get_strictest_role_across_sites( $user_id, array( 'totp', 'email', 'passkey' ) );
120 if ( is_string( $strict_roles ) && '' !== $strict_roles ) {
121 return array( $strict_roles );
122 }
123 if ( is_array( $strict_roles ) ) {
124 return array_values( $strict_roles );
125 }
126 return array();
127 }
128
129 $user = get_userdata( $user_id );
130 $roles = $user->roles;
131 if ( ! is_array( $roles ) ) {
132 $roles = array();
133 }
134 return $roles;
135 }
136
137 /**
138 * Get the user's actual role slugs across the network.
139 *
140 * This is intentionally separate from get_user_roles(), which returns the
141 * strictest role for provider-selection logic. Enforcement needs the real
142 * assigned roles and must not depend on enabled-method state.
143 */
144 private static function get_user_role_slugs_across_network( int $user_id ): array {
145 if ( ! is_multisite() ) {
146 return self::get_user_roles( $user_id );
147 }
148
149 $roles = array();
150 foreach ( get_sites( [ 'fields' => 'ids' ] ) as $blog_id ) {
151 switch_to_blog( (int) $blog_id );
152 $user = get_userdata( $user_id );
153 if ( $user && is_array( $user->roles ) ) {
154 $roles = array_merge( $roles, $user->roles );
155 }
156 restore_current_blog();
157 }
158
159 return array_values( array_unique( $roles ) );
160 }
161
162 /**
163 * Generate a one-time login URL for a user.
164 *
165 * @param int $user_id //the user ID.
166 * @param bool $disable_two_fa //whether to disable two-factor authentication.
167 *
168 * @return string //the generated URL.
169 */
170 public static function rsssl_one_time_login_url( int $user_id, bool $disable_two_fa = false, $profile = false ): string {
171
172 $token = bin2hex( openssl_random_pseudo_bytes( 16 ) ); // 16 bytes * 8 bits/byte = 128 bits.
173 set_transient( 'skip_two_fa_token_' . $user_id, $token, 2 * MINUTE_IN_SECONDS );
174
175 $obfuscated_user_id = self::obfuscate_user_id( $user_id );
176
177 $nonce = wp_create_nonce( 'one_time_login_' . $user_id );
178 if(!$profile) {
179 $args = array(
180 'rsssl_one_time_login' => $obfuscated_user_id,
181 'token' => $token,
182 '_wpnonce' => $nonce,
183 );
184 } else {
185 $args = array(
186 '_wpnonce' => $nonce,
187 'profile' => $profile,
188 );
189 }
190
191 if (function_exists('rsssl_get_option') && rsssl_get_option('change_login_url_enabled') !== false && !empty(rsssl_get_option('change_login_url'))) {
192 $login_url = trailingslashit(site_url()) . rsssl_get_option('change_login_url');
193 } else {
194 $login_url = wp_login_url();
195 }
196
197 if ( $disable_two_fa ) {
198 $args['rsssl_two_fa_disable'] = true;
199 }
200
201 // Return the URL with the added query arguments.
202 return add_query_arg( $args, $profile? get_edit_profile_url( $user_id ):$login_url );
203 }
204
205
206 /**
207 * Get the {method}_role_status. The role with the most weighing status will be returned. empty, optional or forded. Where forced is the most weighing.
208 *
209 * @param string $method //the method to check.
210 * @param int $user_id //the user id to get the roles for.
211 *
212 * @return string
213 */
214 public static function get_role_status( string $method, int $user_id ): string {
215 $roles = array();
216
217 if ( is_multisite() ) {
218 $strict_roles = self::get_strictest_role_across_sites( $user_id, array( $method ) );
219 if ( is_string( $strict_roles ) && '' !== $strict_roles ) {
220 $roles = array( $strict_roles );
221 }
222 if ( is_array( $strict_roles ) ) {
223 $roles = array_values( $strict_roles );
224 }
225 } else {
226 $roles = self::get_user_roles( $user_id );
227 }
228
229 // Early return for non-passkey methods with no roles.
230 if ( empty( $roles ) && 'passkey' !== $method ) {
231 return 'empty';
232 }
233
234 $provider = 'email' === $method ? '_email' : '_' . self::sanitize_method( $method );
235
236 // Check if the method is enabled.
237 $enabled = ($method === 'passkey') ? array_keys( wp_roles()->get_names() ) : rsssl_get_option("two_fa_enabled_roles$provider");
238
239 $forced = false;
240
241 if ( ! $enabled ) {
242 $return = 'empty';
243 }
244
245 // if the role is forced, return forced.
246 if ( self::contains_role_of_type( $method, $roles, 'forced' ) ) {
247 $return = 'forced';
248 $forced = true;
249 }
250
251 // if the method = 'passkeys' and the role is forced, return forced.
252 if ('passkey' === $method && self::contains_role_of_type($method, $roles, 'forced')) {
253 $return = 'forced';
254 $forced = true;
255 }
256
257 //if the method = 'passkey' and the role is enabled, return optional.
258 if ('passkey' === $method && $enabled) {
259 $return = 'optional';
260 }
261
262 // if the role is enabled, return optional.
263 if ( self::contains_role_of_type( $method, $roles, 'enabled' ) && ! $forced ) {
264 $return = 'optional';
265 }
266
267 if ( empty( $return ) ) {
268 $return = 'empty';
269 }
270
271 return $return;
272 }
273
274 public static function get_login_action(?int $user_id = null): string {
275 if ( null === $user_id ) {
276 $user_id = get_current_user_id();
277 }
278
279 $user = get_userdata( $user_id );
280 $loader = Rsssl_Provider_Loader::get_loader();
281 $available_providers = $loader::available_providers();
282 $ProvidersWithStatus = self::addStatusToProviders($available_providers, $user);
283
284 // first we filter if the array gas an active status.
285 $active = array_filter( $ProvidersWithStatus, function ( $provider ) {
286 return 'active' === $provider['status'];
287 } );
288
289 // if the array is not empty, we return the first key.
290 if ( ! empty( $active ) ) {
291 $active = array_keys( $active );
292 return reset( $active );
293 }
294
295 foreach ($available_providers as $method => $provider_class ) {
296 if ( $provider_class::is_enabled( $user ) ) {
297 $user_status = self::get_user_status( $method, $user_id );
298 $role_status = self::get_role_status( $method, $user_id );
299 if ( 'active' === $user_status && ( 'forced' === $role_status
300 || 'optional' === $role_status )
301 ) {
302 return $method; // Return the method directly if active and role status matches.
303 }
304
305 if ( 'open' === $user_status && ( 'forced' === $role_status
306 || 'optional' === $role_status )
307 ) {
308 $grace_period = self::is_user_in_grace_period( $user );
309
310 if ( $grace_period > 0 && 'forced' === $role_status ) {
311 return 'onboarding';
312 }
313
314 if ( 'optional' === $role_status ) {
315 return 'onboarding';
316 }
317
318 return 'expired';
319 }
320
321 // If role is forced and status isn't disabled, return onboarding.
322 if ( 'forced' === $role_status && 'disabled' !== $user_status ) {
323 return 'onboarding';
324 }
325 }
326 }
327
328 return self::get_email_method_action( $user_id ); // Fallback to email or other default behavior.
329 }
330
331 public static function addStatusToProviders( array $providers, $user ): array {
332 $filtered_providers = [];
333 foreach ( $providers as $method => $provider_class ) {
334 if ( $provider_class::is_enabled( $user ) ) {
335 $user_status = self::get_user_status( $method, $user->ID );
336 $role_status = self::get_role_status( $method, $user->ID );
337
338 $filtered_providers[ $method ] = array(
339 'status' => $user_status,
340 'role' => $role_status,
341 'class' => $provider_class,
342 );
343 } else {
344 $filtered_providers[ $method ] = array(
345 'status' => 'disabled',
346 'role' => 'disabled',
347 'class' => $provider_class,
348 );
349 }
350 }
351 return $filtered_providers;
352 }
353
354 /**
355 * Get required action for the email 2fa method.
356 *
357 * @param int $user_id //the user id to get the roles for.
358 *
359 * @return string //email, onboarding or login
360 */
361 public static function get_email_method_action( int $user_id ): string {
362 $email = Rsssl_Two_Factor_Email::get_instance();
363 $grace_period = self::is_user_in_grace_period( get_userdata( $user_id ) );
364 $return = 'login';
365
366 if ( $email::is_enabled( get_userdata( $user_id ) ) ) {
367
368 $user_status = self::get_user_status( 'email', $user_id );
369 $role_status = self::get_role_status( 'email', $user_id );
370
371 if ( 'active' === $user_status ) {
372 // Also check the role status, in case the admin has disabled this for this role.
373 if ( 'forced' === $role_status || 'optional' === $role_status ) {
374 $return = 'email';
375 }
376 }
377
378 if ( 'open' === $user_status ) {
379 // if the role status is forced or optional, we show onboarding.
380 if ( 'forced' === $role_status || 'optional' === $role_status ) {
381
382 // The role is forced. So check if the grace period is over.
383 if ( $grace_period > 0 && 'forced' === $role_status ) {
384 return 'onboarding';
385 }
386
387 if ('optional' === $role_status) {
388 return 'onboarding';
389 }
390
391 return 'expired';
392 }
393 }
394 }
395
396 // if we're here, the email method is not enabled, so we show login.
397 return $return;
398 }
399
400 /**
401 * Validate if the role status and user status are valid.
402 *
403 * @param string $role_status // The role status to check.
404 * @param string $user_status // The user status to check.
405 *
406 * @return bool // Returns true if the role status and user status are valid, otherwise false.
407 */
408 public static function is_role_and_user_status_valid( string $role_status, string $user_status ): bool {
409 return ( 'forced' === $role_status || 'optional' === $role_status ) && ( 'active' === $user_status || 'open' === $user_status );
410 }
411
412 /**
413 * Get the status for a user, based on the method.
414 *
415 * @param string $method //the method to check.
416 * @param int $user_id //the user id to get the roles for.
417 *
418 * @return string //open, active or disabled
419 */
420 public static function get_user_status( string $method, int $user_id ): string {
421 $method = 'email' === $method ? '_email' : '_' . self::sanitize_method( $method );
422
423 // first check if a user meta rsssl_two_fa_status is set.
424 $status = get_user_meta( $user_id, "rsssl_two_fa_status$method", true );
425
426 return self::sanitize_status( $status );
427 }
428
429 /**
430 * Get the roles for a user, based on the method and type.
431 *
432 * @param string $method //the method to check.
433 * @param string $type //the type to check.
434 *
435 * @return array
436 */
437 private static function get_dynamic_roles_variable( string $method, string $type ): array {
438 // store these roles, as this function can be used in large loops.
439 if ( ! self::$roles_loaded ) {
440 // if the option is a boolean we convert it to an array.
441 self::$enabled_roles_totp = rsssl_get_option( 'two_fa_enabled_roles_totp', [] );
442 self::$enabled_roles_email = rsssl_get_option( 'two_fa_enabled_roles_email', [] );
443 // Passkey is always enabled. So all roles are enabled. Use role
444 // slugs, not translatable role labels.
445 self::$enabled_roles_passkey = array_keys( wp_roles()->get_names() );
446 self::$forced_roles = rsssl_get_option( 'two_fa_forced_roles', [] );
447 self::$roles_loaded = true;
448 }
449
450 $method = 'email' === $method ? '_email' : '_' . self::sanitize_method( $method );
451 $type = 'enabled' === $type ? 'enabled' : 'forced';
452
453 $name = $type . '_roles' . $method;
454 $roles_to_check = 'enabled_roles' . $method;
455
456 // if the type is forced, use the forced roles.
457 if ( 'forced' === $type ) {
458 // Intersect the roles with the enabled roles.
459 self::$$name = array_intersect( self::$forced_roles, self::$$roles_to_check );
460 if ( property_exists( self::class, $name ) ) {
461 $roles = self::$$name;
462 if ( ! is_array( $roles ) ) {
463 $roles = array();
464 }
465 return $roles;
466 }
467 }
468
469 // if the type is enabled, use the enabled roles.
470 if ( 'enabled' === $type ) {
471 self::$$name = array_merge( self::$$roles_to_check );
472 if ( property_exists( self::class, $name ) ) {
473 $roles = self::$$name;
474 if ( ! is_array( $roles ) ) {
475 $roles = array();
476 }
477 return $roles;
478 }
479 }
480
481 return array();
482 }
483
484 /**
485 * Check if the array of roles contains a role of type $type, forced or optional.
486 *
487 * @param string $method //the method to check.
488 * @param array $roles //the roles to check.
489 * @param string $type //the type to check.
490 *
491 * @return bool
492 */
493 public static function contains_role_of_type( string $method, array $roles, string $type ): bool {
494 $roles_to_check = self::get_dynamic_roles_variable( $method, $type );
495 foreach ( $roles as $role ) {
496 if ( in_array( $role, $roles_to_check, true ) ) {
497 return true;
498 }
499 }
500 return false;
501 }
502
503 /**
504 * Check if a role is of a certain type, optional or forced
505 *
506 * @param string $method //the method to check.
507 * @param string $role //the role to check.
508 * @param string $type //the type to check.
509 *
510 * @return bool
511 */
512 public static function role_is_of_type( string $method, string $role, string $type ): bool {
513 return self::contains_role_of_type( $method, array( $role ), $type );
514 }
515
516
517 /**
518 * Get the user meta enabled providers key.
519 *
520 * @param string $status //the status to filter by.
521 *
522 * @return string //the user meta key.
523 */
524 protected static function sanitize_status( string $status ): string {
525 return in_array( $status, array( 'open', 'active', 'disabled' ), true ) ? $status : 'open';
526 }
527
528 /**
529 * Get the user meta enabled providers key.
530 *
531 * @param string $method //the method to sanitize.
532 *
533 * @return string
534 */
535 public static function sanitize_method( string $method ): string {
536 return in_array( $method, array( 'email', 'totp', 'passkey' ), true ) ? $method : 'email';
537 }
538
539 /**
540 * Check if a user is forced to use 2FA based on their roles.
541 *
542 * @param int $user_id // the ID of the user to check.
543 *
544 * @return bool // true if the user is forced to use 2FA, false otherwise.
545 */
546 public static function is_user_forced_to_use_2fa( int $user_id ): bool {
547 $roles = self::get_user_role_slugs_across_network( $user_id );
548 $forced_roles = rsssl_get_option( 'two_fa_forced_roles', [] );
549 foreach ( $roles as $role ) {
550 if ( in_array( $role, $forced_roles, true ) ) {
551 return true;
552 }
553 }
554 return false;
555 }
556
557 /**
558 * Check if a user is in the grace period for two-factor authentication.
559 *
560 * @param WP_User $user The user to check.
561 *
562 * @return int|false The number of days remaining in the grace period, or false if the user is not in the grace period.
563 */
564 public static function is_user_in_grace_period( WP_User $user ) {
565
566 $grace_period = rsssl_get_option( 'two_fa_grace_period');
567
568 // if the grace period is not set, return false.
569 if ( ! self::is_user_forced_to_use_2fa( $user->ID ) ) {
570 return false;
571 }
572
573 $last_login = get_user_meta( $user->ID, 'rsssl_two_fa_last_login', true );
574
575
576 if ( $last_login ) {
577 $last_login = strtotime( $last_login );
578 $now = time();
579 $diff = $now - $last_login;
580 $days = floor( $diff / ( 60 * 60 * 24 ) );
581
582 if ( $days < $grace_period ) {
583 $end_date = gmdate( 'Y-m-d', $last_login );
584 // We add the grace period to the last login date.
585 $end_date = date( 'Y-m-d', strtotime( $end_date . ' + ' . $grace_period . ' days' ) );
586 $today = gmdate('Y-m-d', $now);
587 // If the end date is today, return 1.
588 if ($end_date === $today) {
589 return 1;
590 }
591
592 return $grace_period - $days;
593 }
594 // it is now equal or greater, so return false.
595 return false;
596 }
597 // if the last login is not set, return the grace period. but also set the user meta.
598 update_user_meta( $user->ID, 'rsssl_two_fa_last_login', gmdate( 'Y-m-d H:i:s' ) );
599
600 return $grace_period;
601 }
602
603 /**
604 * Get the enabled roles for a user.
605 *
606 * @param int $user_id // The ID of the user.
607 *
608 * @return array // The array of enabled roles for the user.
609 */
610 public static function get_enabled_roles( int $user_id ): array {
611 $roles = self::get_user_roles( $user_id );
612 if(defined('rsssl_pro') && rsssl_pro ) {
613 $totp = rsssl_get_option( 'two_fa_enabled_roles_totp', [] );
614 } else {
615 $totp = [];
616 }
617
618 $email = rsssl_get_option( 'two_fa_enabled_roles_email', [] );
619 $passkey = array_keys( wp_roles()->get_names() );
620 $enabled_roles = array_merge( $totp, $email, $passkey );
621 return array_intersect( $roles, $enabled_roles );
622 }
623
624 /**
625 * Get the enabled roles for a user.
626 * This function is used to get the roles that are enabled for a user.
627 *
628 * @param int $user_id //the user ID to obfuscate.
629 *
630 * @return string
631 */
632 public static function obfuscate_user_id( int $user_id ): string {
633 // Convert the user ID to a string with some noise.
634 $obfuscated = 'user-' . $user_id . '-id';
635 // Encode the string using base64.
636 return base64_encode( $obfuscated );
637 }
638
639 /**
640 * Deobfuscate the user ID for use in URL.
641 *
642 * @param string $data //the data to deobfuscate.
643 *
644 * @return string|null
645 */
646 public static function deobfuscate_user_id( string $data ): ?string {
647 // Decode from base64.
648 $decoded = base64_decode( $data );
649 // Remove the noise to get the user ID.
650 if ( preg_match( '/user-(\d+)-id/', $decoded, $matches ) ) {
651 return $matches[1];
652 }
653
654 return null;
655 }
656
657 /**
658 * Based on the roles enabled return the method for the current user.
659 * If both methods are enabled, return the string not set.
660 * If only one method is enabled, return that method as a string.
661 * If no method is enabled, return the string None.
662 *
663 * @param int $user_id //the user ID to get the roles for.
664 *
665 * @return string
666 */
667 public static function get_enabled_method( int $user_id ): string {
668 $user_id = absint( $user_id ); // make sure an integer and not a float, negative value.
669 $enabled_roles = self::get_enabled_roles( $user_id ) ?? array();
670 $enabled_totp = rsssl_get_option( 'two_fa_enabled_roles_totp', [] );
671 $enabled_email = rsssl_get_option( 'two_fa_enabled_roles_email', [] );
672
673 $totp = array_intersect( $enabled_roles, $enabled_totp );
674 $email = array_intersect( $enabled_roles, $enabled_email );
675
676 if ( ! empty( $totp ) && ! empty( $email ) ) {
677 $enabled_method = __( 'not set', 'really-simple-ssl' );
678 }
679
680 if ( ! empty( $totp ) ) {
681 $enabled_method = __( 'Authenticator App', 'really-simple-ssl' );
682 }
683
684 if ( ! empty( $email ) ) {
685 $enabled_method = __( 'Email', 'really-simple-ssl' );
686 }
687
688 if ( ! isset( $enabled_method ) ) {
689 $enabled_method = __( 'None', 'really-simple-ssl' );
690 }
691
692 return $enabled_method;
693 }
694
695 /**
696 * Get the configured provider for a user based on their ID.
697 *
698 * @param int $user_id The ID of the user.
699 *
700 * @return string The configured provider.
701 */
702 public static function get_configured_provider( int $user_id ): string {
703 // With 2 providers, TOTP and Email we check both options and get the one that is not disabled.
704 $totp_meta = get_user_meta( $user_id, 'rsssl_two_fa_status_totp', true );
705 $email_meta = get_user_meta( $user_id, 'rsssl_two_fa_status', true );
706 $passkey_meta = get_user_meta( $user_id, 'rsssl_two_fa_status_passkey', true );
707 $provider = __( 'None', 'really-simple-ssl' );
708 // if the status is active, return the method.
709 if ( 'active' === $totp_meta ) {
710 $provider = Rsssl_Two_Factor_Totp::NAME;
711 }
712 if ( 'active' === $email_meta ) {
713 $provider = Rsssl_Two_Factor_Email::NAME;
714 }
715 if ('active' === $passkey_meta) {
716 $provider = Rsssl_Two_Factor_Passkey::NAME;
717 }
718 return $provider;
719 }
720
721 /**
722 * Get the backup codes for a user.
723 *
724 * @param int $user_id // The user ID.
725 *
726 * @return array // An array of backup codes.
727 */
728 public static function get_backup_codes( int $user_id ): array {
729 $codes = get_transient( 'rsssl_two_factor_backup_codes_' . $user_id );
730 if ( ! is_array( $codes ) ) {
731 $codes = array();
732 }
733 return $codes;
734 }
735
736 /**
737 * Check if the last login date for a user is today.
738 *
739 * @param WP_User $user //the user.
740 *
741 * @return bool //true if last login date is today, false otherwise.
742 */
743 public static function is_today( WP_User $user ): bool {
744 return (1 === (int) self::is_user_in_grace_period( $user ));
745 }
746
747
748 /**
749 * Ensure that the default roles are first in the array
750 *
751 *
752 * @return array
753 */
754 protected static function sort_roles_by_default_first( array $roles ): array {
755 $default_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
756 $sorted_roles = array();
757 foreach ( $default_roles as $default_role ) {
758 if ( in_array( $default_role, $roles, true ) ) {
759 $sorted_roles[] = $default_role;
760 }
761 }
762 foreach ( $roles as $role ) {
763 if ( ! in_array( $role, $sorted_roles, true ) ) {
764 $sorted_roles[] = $role;
765 }
766 }
767 return $sorted_roles;
768 }
769
770 /**
771 * Get the strictest role across all sites for a given user
772 *
773 * @param int $user_id //the ID of the user.
774 *
775 * @return array|null //returns the strictest role or null if no roles found.
776 */
777 public static function get_strictest_role_across_sites(int $user_id, $methods ): ?array
778 {
779 $sites = get_sites();
780 $all_roles = [];
781
782 foreach ($sites as $site) {
783 switch_to_blog($site->blog_id);
784 $user = get_userdata($user_id);
785 if ($user && is_array($user->roles)) {
786 foreach($user->roles as $role){
787 $all_roles[] = $role;
788 }
789 }
790
791 restore_current_blog();
792 }
793 $all_roles = array_unique($all_roles);
794
795 return self::get_strictest_role($methods, $all_roles);
796 }
797
798 /**
799 * Get the strictest role from a list of roles
800 *
801 * @param array $roles // The list of roles
802 * @return array // The strictest role
803 */
804 protected static function get_strictest_role(array $methods, array $roles): array
805 {
806 $result = [];
807 if (is_multisite()) {
808 $roles = self::sort_roles_by_default_first($roles);
809 $forced_roles = rsssl_get_option('two_fa_forced_roles', []);
810 // if there are forced roles, prioritize them by removing all other roles
811 if (!empty($forced_roles) && array_intersect($roles, $forced_roles)) {
812 $roles = array_intersect($roles, $forced_roles);
813 }
814 }
815
816 foreach ($methods as $method) {
817 // First, prioritize forced roles using the default-first sorting method
818 if (self::contains_role_of_type($method, $roles, 'forced')) {
819 foreach ($roles as $role) {
820 if (self::role_is_of_type($method, $role, 'forced')) {
821 // If forced role is found, assign it to the method and continue to the next method
822 $result[$method] = $role;
823 continue 2;
824 }
825 }
826 }
827
828 // If no forced role, check for optional roles
829 if (self::contains_role_of_type($method, $roles, 'enabled')) {
830 foreach ($roles as $role) {
831 if (self::role_is_of_type($method, $role, 'enabled')) {
832 // If optional role is found, assign it to the method and continue to the next method
833 $result[$method] = $role;
834 continue 2;
835 }
836 }
837 }
838
839 // If no role was found, assign an empty string
840 $result[$method] = '';
841 }
842 //remove empty values
843 return array_values(array_unique(array_filter($result)));
844 }
845
846 /**
847 * Get the user status per method
848 *
849 * @param int $user_id // The ID of the user.
850 *
851 * @return array // The user status per method
852 */
853 public static function get_user_status_per_method(int $user_id): array
854 {
855 $methods = self::get_available_methods();
856 $result = [];
857 foreach ($methods as $method) {
858 $result[$method] = self::get_user_status($method, $user_id);
859 }
860 return $result;
861 }
862
863 private static function get_available_methods(): array
864 {
865 if(defined('rsssl_pro') && !rsssl_pro ) {
866 return ['totp', 'email'];
867 }
868 return ['email'];
869 }
870 }
871
872 new Rsssl_Two_Factor_Settings();
873