Skip to content

Instantly share code, notes, and snippets.

@dkjensen
Created June 5, 2024 14:22
Show Gist options
  • Save dkjensen/da7bde4142ac49968320c1b8bec980f6 to your computer and use it in GitHub Desktop.
Save dkjensen/da7bde4142ac49968320c1b8bec980f6 to your computer and use it in GitHub Desktop.
Enforce Two Factor for Administrators on WordPress
<?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