two-factor-extended/includes/class-cli-commands.php
2026-03-28 09:59:53 +00:00

373 lines
10 KiB
PHP

<?php
/**
* WP-CLI commands
*
* @package TwoFactorExtended
* @since 0.1.0
*/
// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Two_Factor_Extended_CLI_Commands
*
* Provides WP-CLI commands for Two-Factor Extended.
*
* @since 0.1.0
*/
class Two_Factor_Extended_CLI_Commands {
/**
* Display 2FA compliance status.
*
* ## OPTIONS
*
* [--role=<role>]
* : Filter by user role.
*
* [--format=<format>]
* : Output format (table, json, csv). Default: table.
*
* ## EXAMPLES
*
* wp two-factor-extended status
* wp two-factor-extended status --role=administrator
* wp two-factor-extended status --format=json
*
* @since 0.1.0
*
* @param array<int, string> $args Positional arguments.
* @param array<string, string> $assoc_args Associative arguments.
*/
public function status( array $args, array $assoc_args ): void {
$role = $assoc_args['role'] ?? '';
$format = $assoc_args['format'] ?? 'table';
$compliance = two_factor_extended()->get_compliance_report();
if ( null === $compliance ) {
WP_CLI::error( 'Compliance report module is not available.' );
return;
}
$stats = $compliance->get_compliance_stats( array( 'role' => $role ) );
if ( 'json' === $format ) {
WP_CLI::line( wp_json_encode( $stats, JSON_PRETTY_PRINT ) );
return;
}
// Display overview.
WP_CLI::line( WP_CLI::colorize( '%G2FA Compliance Status%n' ) );
WP_CLI::line( str_repeat( '=', 40 ) );
$overview = array(
array(
'Metric' => 'Total Users',
'Count' => $stats['total_users'],
'Percentage' => '100%',
),
array(
'Metric' => 'Compliant',
'Count' => $stats['compliant_users'],
'Percentage' => $this->calculate_percentage( $stats['compliant_users'], $stats['total_users'] ),
),
array(
'Metric' => 'Non-Compliant',
'Count' => $stats['non_compliant'],
'Percentage' => $this->calculate_percentage( $stats['non_compliant'], $stats['total_users'] ),
),
array(
'Metric' => 'In Grace Period',
'Count' => $stats['grace_period'],
'Percentage' => $this->calculate_percentage( $stats['grace_period'], $stats['total_users'] ),
),
array(
'Metric' => 'No Requirements',
'Count' => $stats['no_requirements'],
'Percentage' => $this->calculate_percentage( $stats['no_requirements'], $stats['total_users'] ),
),
);
WP_CLI\Utils\format_items( $format, $overview, array( 'Metric', 'Count', 'Percentage' ) );
// Display by role if available.
if ( ! empty( $stats['by_role'] ) ) {
WP_CLI::line( '' );
WP_CLI::line( WP_CLI::colorize( '%GBy Role%n' ) );
WP_CLI::line( str_repeat( '=', 40 ) );
$by_role = array();
foreach ( $stats['by_role'] as $role_key => $role_stats ) {
$role_name = Two_Factor_Extended_Role_Manager::get_role_display_name( $role_key );
$by_role[] = array(
'Role' => $role_name,
'Total' => $role_stats['total'],
'Compliant' => $role_stats['compliant'],
'Non-Compliant' => $role_stats['non_compliant'],
'Compliance %' => $this->calculate_percentage( $role_stats['compliant'], $role_stats['total'] ),
);
}
WP_CLI\Utils\format_items( $format, $by_role, array( 'Role', 'Total', 'Compliant', 'Non-Compliant', 'Compliance %' ) );
}
}
/**
* Enforce 2FA requirements for a role.
*
* ## OPTIONS
*
* --role=<role>
* : User role to enforce.
*
* [--reset-grace]
* : Reset grace period for all users in role.
*
* ## EXAMPLES
*
* wp two-factor-extended enforce --role=administrator
* wp two-factor-extended enforce --role=editor --reset-grace
*
* @since 0.1.0
*
* @param array<int, string> $args Positional arguments.
* @param array<string, string> $assoc_args Associative arguments.
*/
public function enforce( array $args, array $assoc_args ): void {
if ( empty( $assoc_args['role'] ) ) {
WP_CLI::error( 'Please specify a role with --role=<role>' );
return;
}
$role = $assoc_args['role'];
$reset_grace = isset( $assoc_args['reset-grace'] );
// Get users with this role.
$users = get_users( array( 'role' => $role ) );
if ( empty( $users ) ) {
WP_CLI::warning( sprintf( 'No users found with role: %s', $role ) );
return;
}
$enforcement = two_factor_extended()->get_enforcement();
if ( null === $enforcement ) {
WP_CLI::error( 'Enforcement module is not available.' );
return;
}
$processed = 0;
$progress = WP_CLI\Utils\make_progress_bar( 'Enforcing 2FA requirements', count( $users ) );
foreach ( $users as $user ) {
$required = $enforcement->get_required_providers_for_user( $user->ID );
if ( empty( $required ) ) {
$progress->tick();
continue;
}
// Set or reset enforcement start date.
update_user_meta( $user->ID, Two_Factor_Extended_Enforcement::META_ENFORCEMENT_START, time() );
if ( $reset_grace ) {
delete_user_meta( $user->ID, Two_Factor_Extended_Enforcement::META_GRACE_NOTIFIED );
}
// Log the action.
$audit_log = two_factor_extended()->get_audit_log();
if ( null !== $audit_log ) {
$audit_log->log_event(
'cli_enforce',
sprintf( 'WP-CLI: Enforced 2FA for user %s', $user->user_login ),
$user->ID,
array(
'role' => $role,
'required_providers' => $required,
)
);
}
$processed++;
$progress->tick();
}
$progress->finish();
WP_CLI::success(
sprintf(
'Enforced 2FA requirements for %d users with role: %s',
$processed,
$role
)
);
}
/**
* Generate compliance report.
*
* ## OPTIONS
*
* [--role=<role>]
* : Filter by user role.
*
* [--format=<format>]
* : Output format (table, json, csv). Default: table.
*
* [--non-compliant-only]
* : Show only non-compliant users.
*
* ## EXAMPLES
*
* wp two-factor-extended report
* wp two-factor-extended report --role=administrator
* wp two-factor-extended report --non-compliant-only --format=csv
*
* @since 0.1.0
*
* @param array<int, string> $args Positional arguments.
* @param array<string, string> $assoc_args Associative arguments.
*/
public function report( array $args, array $assoc_args ): void {
$role = $assoc_args['role'] ?? '';
$format = $assoc_args['format'] ?? 'table';
$non_compliant_only = isset( $assoc_args['non-compliant-only'] );
$compliance = two_factor_extended()->get_compliance_report();
if ( null === $compliance ) {
WP_CLI::error( 'Compliance report module is not available.' );
return;
}
if ( $non_compliant_only ) {
$users = $compliance->get_non_compliant_users( array( 'role' => $role ) );
if ( empty( $users ) ) {
WP_CLI::success( 'All users are compliant!' );
return;
}
$items = array();
foreach ( $users as $user_data ) {
$roles_raw = isset( $user_data['roles'] ) && is_array( $user_data['roles'] ) ? $user_data['roles'] : array();
$missing_providers_raw = isset( $user_data['missing_providers'] ) && is_array( $user_data['missing_providers'] ) ? $user_data['missing_providers'] : array();
$roles = array_filter( $roles_raw, 'is_string' );
$missing_providers = array_filter( $missing_providers_raw, 'is_string' );
$items[] = array(
'User ID' => $user_data['user_id'],
'Username' => $user_data['user_login'],
'Email' => $user_data['user_email'],
'Roles' => implode( ', ', $roles ),
'Missing Providers' => implode( ', ', $missing_providers ),
'Grace Period' => $user_data['in_grace_period'] ? 'Yes' : 'No',
'Days Remaining' => $user_data['grace_remaining'],
);
}
WP_CLI\Utils\format_items(
$format,
$items,
array( 'User ID', 'Username', 'Email', 'Roles', 'Missing Providers', 'Grace Period', 'Days Remaining' )
);
} else {
// Show full statistics.
$this->status( $args, $assoc_args );
}
}
/**
* Reset 2FA grace period for a user.
*
* ## OPTIONS
*
* --user=<user>
* : User ID, login, or email.
*
* ## EXAMPLES
*
* wp two-factor-extended reset --user=admin
* wp two-factor-extended reset --user=123
* wp two-factor-extended reset --user=admin@example.com
*
* @since 0.1.0
*
* @param array<int, string> $args Positional arguments.
* @param array<string, string> $assoc_args Associative arguments.
*/
public function reset( array $args, array $assoc_args ): void {
if ( empty( $assoc_args['user'] ) ) {
WP_CLI::error( 'Please specify a user with --user=<user>' );
return;
}
// Get user by ID, login, or email.
$user_identifier = $assoc_args['user'];
if ( is_numeric( $user_identifier ) ) {
$user = get_userdata( (int) $user_identifier );
} elseif ( is_email( $user_identifier ) ) {
$user = get_user_by( 'email', $user_identifier );
} else {
$user = get_user_by( 'login', $user_identifier );
}
if ( ! $user ) {
WP_CLI::error( sprintf( 'User not found: %s', $user_identifier ) );
return;
}
// Reset enforcement start date.
update_user_meta( $user->ID, Two_Factor_Extended_Enforcement::META_ENFORCEMENT_START, time() );
// Clear grace period notified flag.
delete_user_meta( $user->ID, Two_Factor_Extended_Enforcement::META_GRACE_NOTIFIED );
// Log the action.
$audit_log = two_factor_extended()->get_audit_log();
if ( null !== $audit_log ) {
$audit_log->log_event(
'cli_reset_grace',
sprintf( 'WP-CLI: Reset grace period for user %s', $user->user_login ),
$user->ID
);
}
WP_CLI::success(
sprintf(
'Grace period reset for user: %s (ID: %d)',
$user->user_login,
$user->ID
)
);
}
/**
* Calculate percentage.
*
* @since 0.1.0
*
* @param int $part Part value.
* @param int $total Total value.
*
* @return string Percentage string.
*/
private function calculate_percentage( int $part, int $total ): string {
if ( 0 === $total ) {
return '0%';
}
return sprintf( '%.1f%%', ( $part / $total ) * 100 );
}
}