Created
June 5, 2024 14:22
-
-
Save dkjensen/da7bde4142ac49968320c1b8bec980f6 to your computer and use it in GitHub Desktop.
Enforce Two Factor for Administrators on WordPress
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Plugin Name: Force Two Factor | |
* Description: Force Two Factor Authentication for some users. | |
* Requires at least: 6.0.2 | |
* Requires PHP: 7.0 | |
* Version: 0.0.0-development | |
* Author: CloudCatch LLC | |
* Author URI: https://cloudcatch.io | |
* License: GPL v2 or later | |
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt | |
* Text Domain: force-two-factor | |
* | |
* @package CloudCatch\ForceTwoFactor | |
*/ | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; | |
} | |
const FORCE_TWO_FACTOR_CAP = 'manage_options'; | |
// Do not allow API requests from 2fa users. | |
add_filter( 'two_factor_user_api_login_enable', '__return_false', 1 ); | |
/** | |
* Enforce two-factor authentication for some users. | |
* | |
* @return void | |
*/ | |
function cc_enforce_two_factor_plugin() { | |
if ( class_exists( 'Two_Factor_Core' ) && is_user_logged_in() ) { | |
$limited = current_user_can( FORCE_TWO_FACTOR_CAP ); | |
// Calculate current_user_can outside map_meta_cap to avoid callback loop. | |
add_filter( | |
'cc_is_two_factor_forced', | |
function () use ( $limited ) { | |
return $limited; | |
}, | |
9 | |
); | |
$is_user_using_two_factor = Two_Factor_Core::is_user_using_two_factor(); | |
add_filter( | |
'cc_is_user_using_two_factor', | |
function () use ( $is_user_using_two_factor ) { | |
return $is_user_using_two_factor; | |
} | |
); | |
add_action( 'admin_notices', 'cc_two_factor_admin_notice' ); | |
add_filter( 'map_meta_cap', 'cc_two_factor_filter_caps', 0, 4 ); | |
} | |
} | |
add_action( 'set_current_user', 'cc_enforce_two_factor_plugin' ); | |
/** | |
* Check if two-factor authentication should be forced for the current user. | |
* | |
* @return bool | |
*/ | |
function cc_should_force_two_factor() { | |
if ( ! class_exists( 'Two_Factor_Core' ) ) { | |
return false; | |
} | |
if ( apply_filters( 'cc_is_user_using_two_factor', false ) ) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Check if two-factor authentication is forced for the current user. | |
* | |
* @return bool | |
*/ | |
function cc_is_two_factor_forced() { | |
if ( ! cc_should_force_two_factor() ) { | |
return false; | |
} | |
return apply_filters( 'cc_is_two_factor_forced', false ); | |
} | |
/** | |
* Filter the capabilities of the current user. | |
* | |
* @param array $caps The user's capabilities. | |
* @param string $cap The capability being checked. | |
* @param int $user_id The user ID. | |
* @param array $args Adds the context to the cap. Typically the object ID. | |
* | |
* @return array | |
*/ | |
function cc_two_factor_filter_caps( $caps, $cap, $user_id, $args ) { | |
// If the machine user is not defined or the current user is not the machine user, don't filter caps. | |
if ( cc_is_two_factor_forced() ) { | |
$subscriber_caps = array( | |
'read', | |
'level_0', | |
); | |
// You can edit your own user account (required to set up 2FA). | |
if ( 'edit_user' === $cap && ! empty( $args ) && (int) $user_id === (int) $args[0] ) { | |
$subscriber_caps[] = 'edit_user'; | |
} | |
// WooCommerce caps to check. | |
$woocommerce_caps = array( | |
'edit_posts', | |
'manage_woocommerce', | |
'view_admin_dashboard', | |
); | |
// Track whether or not we've already granted this user wp-admin access based on WC standards. | |
static $user_should_have_wc_admin_access = false; | |
// If we haven't granted access yet, and this $cap is a WC cap to check. | |
if ( ! $user_should_have_wc_admin_access && in_array( $cap, $woocommerce_caps, true ) ) { | |
// If this user has this $cap and it's `true`, grant this user wp-admin access. | |
if ( isset( wp_get_current_user()->allcaps[ $cap ] ) && true === wp_get_current_user()->allcaps[ $cap ] ) { | |
$user_should_have_wc_admin_access = true; | |
add_filter( 'woocommerce_prevent_admin_access', '__return_false' ); | |
} | |
} | |
if ( ! in_array( $cap, $subscriber_caps, true ) ) { | |
return array( 'do_not_allow' ); | |
} | |
} | |
return $caps; | |
} | |
/** | |
* Check if the notice should be shown on the current screen. | |
* | |
* @return bool | |
*/ | |
function cc_should_show_notice_on_current_screen() { | |
$screen = get_current_screen(); | |
// Don't show on the "Edit Post" screen as it interferes with the Block Editor. | |
if ( $screen->is_block_editor() ) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Display an admin notice to force two-factor authentication. | |
* | |
* @return void | |
*/ | |
function cc_two_factor_admin_notice() { | |
if ( ! cc_is_two_factor_forced() ) { | |
return; | |
} | |
if ( ! cc_should_show_notice_on_current_screen() ) { | |
return; | |
} | |
?> | |
<div class="notice notice-error"> | |
<p> | |
<?php esc_html_e( 'Your account requires two-factor authentication to be enabled. For the safety and security of this site, your account access has been downgraded. Please enable two-factor authentication to restore your access.', 'force-two-factor' ); ?> | |
</p> | |
<p> | |
<a href="<?php echo esc_url( admin_url( 'profile.php#two-factor-options' ) ); ?>" class="button button-primary"> | |
<?php esc_html_e( 'Enable Two-factor Authentication', 'force-two-factor' ); ?> | |
</a> | |
</p> | |
</div> | |
<?php | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment