v1.0.0
This commit is contained in:
parent
3291a5f7d2
commit
212d64abfa
27 changed files with 8654 additions and 2 deletions
368
includes/class-enforcement.php
Normal file
368
includes/class-enforcement.php
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
<?php
|
||||
/**
|
||||
* 2FA requirement enforcement class
|
||||
*
|
||||
* @package TwoFactorExtended
|
||||
* @since 0.1.0
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Two_Factor_Extended_Enforcement
|
||||
*
|
||||
* Enforces 2FA requirements based on user roles.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
class Two_Factor_Extended_Enforcement {
|
||||
|
||||
/**
|
||||
* User meta key for enforcement start date.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var string
|
||||
*/
|
||||
const META_ENFORCEMENT_START = 'two_factor_extended_enforcement_start';
|
||||
|
||||
/**
|
||||
* User meta key for grace period notified.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var string
|
||||
*/
|
||||
const META_GRACE_NOTIFIED = 'two_factor_extended_grace_notified';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WordPress hooks.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
private function init_hooks(): void {
|
||||
add_filter( 'authenticate', array( $this, 'check_user_requirements' ), 30, 1 );
|
||||
add_action( 'admin_notices', array( $this, 'display_user_notices' ) );
|
||||
add_action( 'two_factor_extended_daily_check', array( $this, 'check_all_users_compliance' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user's 2FA requirements on login.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param WP_User|WP_Error|null $user User object or error.
|
||||
*
|
||||
* @return WP_User|WP_Error User object or error if requirements not met.
|
||||
*/
|
||||
public function check_user_requirements( $user ) {
|
||||
// Skip if not a user object.
|
||||
if ( ! $user instanceof WP_User ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Skip if Two Factor not available.
|
||||
if ( ! class_exists( 'Two_Factor_Core' ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Get required providers for user.
|
||||
$required_providers = $this->get_required_providers_for_user( $user->ID );
|
||||
|
||||
if ( empty( $required_providers ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Check if user is in grace period.
|
||||
if ( $this->is_in_grace_period( $user->ID ) ) {
|
||||
$this->set_grace_period_notice( $user->ID );
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Check if user meets requirements.
|
||||
if ( ! $this->user_meets_requirements( $user->ID, $required_providers ) ) {
|
||||
return new WP_Error(
|
||||
'two_factor_extended_required',
|
||||
sprintf(
|
||||
/* translators: %s: Required providers list */
|
||||
__( 'Your account requires the following 2FA methods to be configured: %s. Please contact an administrator for assistance.', 'two-factor-extended' ),
|
||||
implode( ', ', $this->get_provider_labels( $required_providers ) )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required providers for user based on their roles.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return array Array of required provider class names.
|
||||
*/
|
||||
public function get_required_providers_for_user( int $user_id ): array {
|
||||
$required = array();
|
||||
|
||||
// Check if user is super admin (Multisite).
|
||||
if ( is_multisite() && Two_Factor_Extended_Role_Manager::is_super_admin( $user_id ) ) {
|
||||
$network_settings = get_site_option( TWO_FACTOR_EXTENDED_NETWORK_OPTION_SETTINGS, array() );
|
||||
|
||||
if ( isset( $network_settings['super_admin_requirements'] ) && is_array( $network_settings['super_admin_requirements'] ) ) {
|
||||
$required = array_merge( $required, $network_settings['super_admin_requirements'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Check network-wide settings (Multisite).
|
||||
if ( is_multisite() ) {
|
||||
$network_settings = get_site_option( TWO_FACTOR_EXTENDED_NETWORK_OPTION_SETTINGS, array() );
|
||||
|
||||
if ( ! empty( $network_settings['enforce_network_wide'] ) ) {
|
||||
// Network enforcement is enabled.
|
||||
$user_roles = Two_Factor_Extended_Role_Manager::get_user_roles( $user_id );
|
||||
|
||||
if ( isset( $network_settings['role_requirements'] ) && is_array( $network_settings['role_requirements'] ) ) {
|
||||
foreach ( $user_roles as $role ) {
|
||||
if ( isset( $network_settings['role_requirements'][ $role ] ) && is_array( $network_settings['role_requirements'][ $role ] ) ) {
|
||||
$required = array_merge( $required, $network_settings['role_requirements'][ $role ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If site override is not allowed, return only network requirements.
|
||||
if ( empty( $network_settings['allow_site_override'] ) ) {
|
||||
return array_unique( $required );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get site-level requirements.
|
||||
$settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() );
|
||||
$user_roles = Two_Factor_Extended_Role_Manager::get_user_roles( $user_id );
|
||||
|
||||
if ( ! empty( $user_roles ) && isset( $settings['role_requirements'] ) ) {
|
||||
foreach ( $user_roles as $role ) {
|
||||
if ( isset( $settings['role_requirements'][ $role ] ) ) {
|
||||
$role_requirements = $settings['role_requirements'][ $role ];
|
||||
|
||||
if ( is_array( $role_requirements ) ) {
|
||||
$required = array_merge( $required, $role_requirements );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique( $required );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user meets 2FA requirements.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param array $required_providers Required provider class names.
|
||||
*
|
||||
* @return bool True if user meets requirements, false otherwise.
|
||||
*/
|
||||
public function user_meets_requirements( int $user_id, array $required_providers ): bool {
|
||||
if ( empty( $required_providers ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$enabled_providers = Two_Factor_Extended_Provider_Detector::get_user_enabled_providers( $user_id );
|
||||
|
||||
foreach ( $required_providers as $provider_class ) {
|
||||
if ( ! isset( $enabled_providers[ $provider_class ] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is in grace period.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return bool True if in grace period, false otherwise.
|
||||
*/
|
||||
public function is_in_grace_period( int $user_id ): bool {
|
||||
$settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() );
|
||||
$grace_days = isset( $settings['grace_period_days'] ) ? (int) $settings['grace_period_days'] : 0;
|
||||
|
||||
// No grace period configured.
|
||||
if ( 0 === $grace_days ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start_date = get_user_meta( $user_id, self::META_ENFORCEMENT_START, true );
|
||||
|
||||
// No enforcement start date set - set it now.
|
||||
if ( empty( $start_date ) ) {
|
||||
update_user_meta( $user_id, self::META_ENFORCEMENT_START, time() );
|
||||
return true;
|
||||
}
|
||||
|
||||
$days_elapsed = ( time() - (int) $start_date ) / DAY_IN_SECONDS;
|
||||
|
||||
return $days_elapsed < $grace_days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remaining grace period days.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return int Remaining days in grace period.
|
||||
*/
|
||||
public function get_grace_period_remaining_days( int $user_id ): int {
|
||||
$settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() );
|
||||
$grace_days = isset( $settings['grace_period_days'] ) ? (int) $settings['grace_period_days'] : 0;
|
||||
$start_date = get_user_meta( $user_id, self::META_ENFORCEMENT_START, true );
|
||||
|
||||
if ( empty( $start_date ) || 0 === $grace_days ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$days_elapsed = ( time() - (int) $start_date ) / DAY_IN_SECONDS;
|
||||
$remaining = $grace_days - $days_elapsed;
|
||||
|
||||
return max( 0, (int) ceil( $remaining ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set grace period notice flag.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*/
|
||||
private function set_grace_period_notice( int $user_id ): void {
|
||||
update_user_meta( $user_id, self::META_GRACE_NOTIFIED, time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display user notices for 2FA requirements.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public function display_user_notices(): void {
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( ! $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$required_providers = $this->get_required_providers_for_user( $user_id );
|
||||
|
||||
if ( empty( $required_providers ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user meets requirements.
|
||||
if ( $this->user_meets_requirements( $user_id, $required_providers ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if in grace period.
|
||||
if ( $this->is_in_grace_period( $user_id ) ) {
|
||||
$remaining_days = $this->get_grace_period_remaining_days( $user_id );
|
||||
|
||||
?>
|
||||
<div class="notice notice-warning">
|
||||
<p>
|
||||
<strong><?php esc_html_e( 'Two Factor Extended:', 'two-factor-extended' ); ?></strong>
|
||||
<?php
|
||||
printf(
|
||||
esc_html(
|
||||
/* translators: 1: Number of days, 2: Required providers list */
|
||||
_n(
|
||||
'You have %1$d day remaining to configure the required 2FA methods: %2$s',
|
||||
'You have %1$d days remaining to configure the required 2FA methods: %2$s',
|
||||
$remaining_days,
|
||||
'two-factor-extended'
|
||||
)
|
||||
),
|
||||
(int) $remaining_days,
|
||||
esc_html( implode( ', ', $this->get_provider_labels( $required_providers ) ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<a href="<?php echo esc_url( admin_url( 'profile.php#two-factor-options' ) ); ?>" class="button button-primary">
|
||||
<?php esc_html_e( 'Configure 2FA Now', 'two-factor-extended' ); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<div class="notice notice-error">
|
||||
<p>
|
||||
<strong><?php esc_html_e( 'Two Factor Extended:', 'two-factor-extended' ); ?></strong>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: Required providers list */
|
||||
esc_html__( 'Your account requires the following 2FA methods: %s. Your access may be restricted until you configure them.', 'two-factor-extended' ),
|
||||
esc_html( implode( ', ', $this->get_provider_labels( $required_providers ) ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<a href="<?php echo esc_url( admin_url( 'profile.php#two-factor-options' ) ); ?>" class="button button-primary">
|
||||
<?php esc_html_e( 'Configure 2FA Now', 'two-factor-extended' ); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider labels from class names.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array $provider_classes Array of provider class names.
|
||||
*
|
||||
* @return array Array of provider labels.
|
||||
*/
|
||||
private function get_provider_labels( array $provider_classes ): array {
|
||||
$names = Two_Factor_Extended_Provider_Detector::get_provider_names();
|
||||
$labels = array();
|
||||
|
||||
foreach ( $provider_classes as $class ) {
|
||||
$labels[] = $names[ $class ] ?? $class;
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check compliance for all users (scheduled task).
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public function check_all_users_compliance(): void {
|
||||
// This will be implemented for reporting/audit purposes.
|
||||
// For now, it's a placeholder for the daily scheduled check.
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue