294 lines
10 KiB
PHP
294 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* This file contains the Rsssl_Two_Factor_Admin class.
|
|
*
|
|
* The Rsssl_Two_Factor_Admin class is responsible for handling the administrative
|
|
* aspects of the two-factor authentication feature in the Really Simple SSL plugin.
|
|
* It includes two_fa_provider for displaying the two-factor authentication settings in the
|
|
* admin area, handling user input, and managing user roles and capabilities related
|
|
* to two-factor authentication.
|
|
*
|
|
* PHP version 7.2
|
|
*
|
|
* @category Security
|
|
* @package Really_Simple_SSL
|
|
* @author Really Simple SSL
|
|
*/
|
|
|
|
namespace RSSSL\Security\WordPress\Two_Fa;
|
|
|
|
use RSSSL\Security\WordPress\Two_Fa\Controllers\Rsssl_Two_Fa_User_Controller;
|
|
use RSSSL\Security\WordPress\Two_Fa\Models\Rsssl_Two_FA_Data_Parameters;
|
|
use RSSSL\Security\WordPress\Two_Fa\Models\Rsssl_Two_FA_user;
|
|
use RSSSL\Security\WordPress\Two_Fa\Repositories\Rsssl_Two_Fa_User_Repository;
|
|
use RSSSL\Security\WordPress\Two_Fa\Services\Rsssl_Two_Fa_Forced_Role_Service;
|
|
use RSSSL\Security\WordPress\Two_Fa\Services\Rsssl_Callback_Queue;
|
|
|
|
use RSSSL\Pro\Security\WordPress\Passkey\Models\Rsssl_Webauthn;
|
|
use WP_User;
|
|
|
|
/**
|
|
* The Rsssl_Two_Factor_Admin class is responsible for handling the administrative
|
|
* aspects of the two-factor authentication feature in the Really Simple SSL plugin.
|
|
* It includes two_fa_provider for displaying the two-factor authentication settings in the
|
|
* admin area, handling user input, and managing user roles and capabilities related
|
|
* to two-factor authentication.
|
|
*
|
|
* @category Security
|
|
* @package Really_Simple_SSL
|
|
* @subpackage Two_Factor
|
|
*/
|
|
class Rsssl_Two_Factor_Admin {
|
|
/**
|
|
* The Rsssl_Two_Factor_Admin instance.
|
|
*
|
|
* @var Rsssl_Two_Factor_Settings $instance The settings object.
|
|
*/
|
|
private static $instance;
|
|
|
|
private Rsssl_Callback_Queue $queue;
|
|
|
|
/**
|
|
* The constructor.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
// if the user is not logged in, it don't need to do anything.
|
|
if (!rsssl_admin_logged_in()) {
|
|
return;
|
|
}
|
|
if (isset(self::$instance)) {
|
|
wp_die();
|
|
}
|
|
self::$instance = $this;
|
|
add_filter('rsssl_do_action', [$this, 'two_fa_table'], 10, 3);
|
|
add_filter('rsssl_after_save_field', [$this, 'change_disabled_users_when_forced'], 20, 3);
|
|
add_filter('rsssl_after_save_field', [$this, 'process_added_removed_enabled_roles'], 20, 3);
|
|
add_filter('rsssl_after_save_field', [$this, 'set_passkey_table'], 20, 3);
|
|
$this->queue = new Rsssl_Callback_Queue();
|
|
$this->queue->process_tasks(1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the passkey table.
|
|
*/
|
|
public function set_passkey_table(string $field_id, $new_value, $prev_value ): void
|
|
{
|
|
// checking if the field is the passkey enabled field
|
|
if ('enable_passkey_login' === $field_id) {
|
|
// if the passkey is enabled, it needs to set the passkey table.
|
|
if ($new_value) {
|
|
new Rsssl_Webauthn(); // Initialize the Webauthn class. It will install everything needed.
|
|
do_action('rsssl_install_tables');
|
|
}
|
|
//TODO think of what needs to be done when the passkey is disabled.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change the disabled status of users when forced.
|
|
*
|
|
* @param string $field_id The ID of the field being changed.
|
|
* @param mixed $new_value The new value of the field.
|
|
* @param array $prev_value The previous value of the field.
|
|
* @return void
|
|
*/
|
|
public function change_disabled_users_when_forced( string $field_id, $new_value, $prev_value = [] ): void {
|
|
if ( 'two_fa_forced_roles' !== $field_id
|
|
|| empty($new_value)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
//making sure that the new value is an array as well as the old value
|
|
if (!is_array($new_value)) {
|
|
$new_value = [];
|
|
}
|
|
|
|
if (!is_array($prev_value)) {
|
|
$prev_value = [];
|
|
}
|
|
|
|
$changedRoles = Rsssl_Two_Fa_Forced_Role_Service::getForForcedRolesChange($prev_value, $new_value);
|
|
|
|
// If no roles have changed, return early.
|
|
if(empty($changedRoles)) {
|
|
return;
|
|
}
|
|
|
|
// Set up initial batch parameters.
|
|
$batch_size = 500;
|
|
$offset = 0;
|
|
$params = new Rsssl_Two_FA_Data_Parameters([
|
|
'filter_column' => 'user_role',
|
|
'filter_value' => 'all',
|
|
'number' => $batch_size,
|
|
'offset' => $offset,
|
|
]);
|
|
// Add the first processing task to the queue.
|
|
$this->queue->add_task([$this, 'process_users_batch'], [ $changedRoles, $params, $batch_size, $offset, 'open' ]);
|
|
$this->queue->add_task([$this, 'process_users_batch'], [ $changedRoles, $params, $batch_size, $offset, 'disabled']);
|
|
}
|
|
|
|
/**
|
|
* Process a batch of forced two-factor users with disabled status.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function process_users_batch(array $changedRoles, Rsssl_Two_FA_Data_Parameters $params, int $batch_size, int $offset, string $status): void {
|
|
$collection = (new Rsssl_Two_Fa_Forced_Role_Service($params))->processBatch($changedRoles, $status);
|
|
|
|
foreach ($collection->getUsers() as $user) {
|
|
$statusForUser = $user->getStatus();
|
|
if (in_array($statusForUser, ['open', 'disabled','expired'])) {
|
|
// Check if the user has a role that has been changed.
|
|
$rolesForUser = $user->getRoles();
|
|
$matchingRoles = array_intersect($rolesForUser, $changedRoles);
|
|
if (!empty($matchingRoles)) {
|
|
// Reset the user's status.
|
|
$user->resetStatus();
|
|
//temp meta key for testing
|
|
update_user_meta($user->getId(), 'rsssl_two_fa_status_reset', true);
|
|
}
|
|
}
|
|
}
|
|
// Check if there are more users to process.
|
|
// The collection contains the total number of records (set in the repository).
|
|
$total = $collection->getTotalRecords();
|
|
if (($params->offset + $params->number) < $total) {
|
|
// Update the offset for the next batch.
|
|
$newOffset = $offset + $batch_size;
|
|
// Queue the next task with the correct new offset.
|
|
$this->queue->add_task([$this, 'process_users_batch'], [$changedRoles, $params, $batch_size, $newOffset, $status]);
|
|
}
|
|
}
|
|
|
|
public function process_added_removed_enabled_roles(string $field_id, $new_value, $prev_value = [])
|
|
{
|
|
if ( 'two_fa_enabled_roles_email' !== $field_id
|
|
|| empty($new_value)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if ( 'two_fa_enabled_roles_totp' !== $field_id
|
|
|| empty($new_value)
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the user can use two-factor authentication (2FA).
|
|
*
|
|
* @return bool Returns true if the user can use 2FA, false otherwise.
|
|
*/
|
|
public function can_i_use_2fa(): bool
|
|
{
|
|
return rsssl_get_option('login_protection_enabled');
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a captcha notice array.
|
|
*
|
|
* This method creates and returns an array representing a captcha notice.
|
|
*
|
|
* @param string $title The title of the notice.
|
|
* @param string $msg The message of the notice.
|
|
*
|
|
* @return array The captcha notice array.
|
|
*/
|
|
private function create_2fa_notice( string $title, string $msg ): array {
|
|
return array(
|
|
'callback' => '_true_',
|
|
'score' => 1,
|
|
'show_with_options' => array( 'login_protection_enabled' ),
|
|
'output' => array(
|
|
'true' => array(
|
|
'title' => $title,
|
|
'msg' => $msg,
|
|
'icon' => 'warning',
|
|
'type' => 'open',
|
|
'dismissible' => true,
|
|
'admin_notice' => false,
|
|
'plusone' => true,
|
|
'highlight_field_id' => 'two_fa_enabled_roles',
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Reset the two-factor authentication for a user.
|
|
*
|
|
* @param array $response The response array.
|
|
* @param string $action The action being performed.
|
|
* @param array $data The data array.
|
|
*
|
|
* @return array The updated response array.
|
|
*/
|
|
public static function reset_user_two_fa( array $response, string $action, array $data ): array {
|
|
if ( ! rsssl_user_can_manage() ) {
|
|
return $response;
|
|
}
|
|
if ( 'two_fa_table' === $action ) {
|
|
// if the user has been disabled, it needs to reset the two-factor authentication.
|
|
$user = get_user_by( 'id', $data['user_id'] );
|
|
if ( $user ) {
|
|
// Delete all 2fa related user meta.
|
|
Rsssl_Two_Fa_Status::delete_two_fa_meta( $user->ID );
|
|
// Set the last login to now, so the user will be forced to use 2fa.
|
|
update_user_meta( $user->ID, 'rsssl_two_fa_last_login', gmdate( 'Y-m-d H:i:s' ) );
|
|
}
|
|
}
|
|
return $response;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generates the two-factor authentication table data based on the action and data parameters.
|
|
*
|
|
* @param array $response The initial response data.
|
|
* @param string $action The action to perform.
|
|
* @param array $data The data needed for the action.
|
|
*
|
|
* @return array The updated response data.
|
|
*/
|
|
public function two_fa_table(array $response, string $action, array $data): array
|
|
{
|
|
$new_response = $response;
|
|
if (rsssl_user_can_manage()) {
|
|
switch ($action) {
|
|
case 'two_fa_table':
|
|
$data_parameters = new Rsssl_Two_FA_Data_Parameters($data);
|
|
$userRepository = new Rsssl_Two_Fa_User_Repository();
|
|
// Create the controller.
|
|
return (new Rsssl_Two_Fa_User_Controller($userRepository))->getUsersForAdminOverview($data_parameters);
|
|
case 'two_fa_reset_user':
|
|
// if the user has been disabled, it needs to reset the two-factor authentication.
|
|
$user = get_user_by('id', $data['id']);
|
|
|
|
if ($user) {
|
|
// Delete all 2fa related user meta.
|
|
Rsssl_Two_Fa_Status::delete_two_fa_meta($user->ID);
|
|
// Set the rsssl_two_fa_last_login to now, so the user will be forced to use 2fa.
|
|
update_user_meta($user->ID, 'rsssl_two_fa_last_login', gmdate('Y-m-d H:i:s'));
|
|
}
|
|
if (!$user) {
|
|
$new_response['request_success'] = false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Default case if no action matches.
|
|
break;
|
|
}
|
|
}
|
|
return $new_response;
|
|
}
|
|
}
|