Done !
| Server IP : 46.105.57.169 / Your IP : 216.73.216.67 Web Server : Apache System : Linux webm002.cluster120.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64 User : verseaumee ( 152031) PHP Version : 8.5.7 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/verseaumee/123click/assets/ |
Upload File : |
includes/index.php 0000644 00000000046 15075513060 0010174 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/functions/index.php 0000644 00000000046 15075513060 0012204 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/functions/core.php 0000644 00000020524 15075513060 0012030 0 ustar 00 <?php
/**
* Core plugin functionality.
*
* @package WP2FA
*/
namespace WP2FA\Core;
use WP2FA\WP2FA;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Views\Re_Login_2FA;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
/**
* Default setup routine
*
* @return void
*/
function setup() {
$n = function ( $function ) {
return __NAMESPACE__ . "\\$function";
};
add_action( 'init', $n( 'i18n' ) );
add_action( 'init', $n( 'init' ) );
add_action( 'admin_enqueue_scripts', $n( 'admin_scripts' ) );
add_action( 'admin_enqueue_scripts', $n( 'admin_styles' ) );
// Hook to allow async or defer on asset loading.
add_filter( 'script_loader_tag', $n( 'script_loader_tag' ), 10, 2 );
/**
* Fires after the plugin is loaded.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'loaded' );
}
/**
* Registers the default textdomain.
*
* @return void
*/
function i18n() {
$locale = apply_filters( 'plugin_locale', get_locale(), 'wp-2fa' );
load_textdomain( 'wp-2fa', WP_LANG_DIR . '/wp-2fa/wp-2fa-' . $locale . '.mo' );
load_plugin_textdomain( 'wp-2fa', false, plugin_basename( WP_2FA_PATH ) . '/languages/' );
}
/**
* Initializes the plugin and fires an action other plugins can hook into.
*
* @return void
*/
function init() {
/**
* Fires when plugin is initiated.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'init' );
}
/**
* Activate the plugin
*
* @return void
*/
function activate() {
// First load the init scripts in case any rewrite functionality is being loaded.
init();
flush_rewrite_rules();
// Check if the user is allowed to manage options for the site.
if ( current_user_can( 'manage_options' ) ) {
// Add an option to let our plugin know this user has not been through the setup wizard.
Settings_Utils::update_option( 'redirect_on_activate', true );
}
// Add plugin version to wp_options.
Settings_Utils::update_option( 'plugin_version', WP_2FA_VERSION );
}
/**
* Deactivate the plugin
*
* Uninstall routines should be in uninstall.php
*
* @return void
*/
function deactivate() {
}
/**
* Uninstall the plugin
*
* @return void
*/
function uninstall() {
WP2FA::init();
if ( ! empty( WP2FA::get_wp2fa_general_setting( 'delete_data_upon_uninstall' ) ) ) {
// Delete settings from wp_options.
if ( WP_Helper::is_multisite() ) {
$network_id = get_current_network_id();
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"
DELETE FROM $wpdb->sitemeta
WHERE meta_key LIKE %s
AND site_id = %d
",
array(
'%wp_2fa_%',
$network_id,
)
)
);
} else {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"
DELETE FROM $wpdb->options
WHERE option_name LIKE %s
",
array(
'%wp_2fa_%',
)
)
);
}
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"
DELETE FROM $wpdb->usermeta
WHERE 1
AND meta_key LIKE %s
",
array(
WP_2FA_PREFIX . 'wp_2fa_%',
)
)
);
}
}
/**
* The list of knows contexts for enqueuing scripts/styles.
*
* @return array
*/
function get_enqueue_contexts() {
return array( 'admin', 'frontend', 'shared' );
}
/**
* Generate an URL to a script, taking into account whether SCRIPT_DEBUG is enabled.
*
* @param string $script Script file name (no .js extension).
* @param string $context Context for the script ('admin', 'frontend', or 'shared').
*
* @return string|\WP_Error URL
*/
function script_url( $script, $context ) {
if ( ! in_array( $context, get_enqueue_contexts(), true ) ) {
return new \WP_Error( 'invalid_enqueue_context', 'Invalid $context specified in WP2FA script loader.' );
}
return WP_2FA_URL . 'dist/js/' . $script . '.js';
}
/**
* Generate an URL to a stylesheet, taking into account whether SCRIPT_DEBUG is enabled.
*
* @param string $stylesheet Stylesheet file name (no .css extension).
* @param string $context Context for the script ('admin', 'frontend', or 'shared').
*
* @return string|\WP_Error URL
*/
function style_url( $stylesheet, $context ) {
if ( ! in_array( $context, get_enqueue_contexts(), true ) ) {
return new \WP_Error( 'invalid_enqueue_context', 'Invalid $context specified in WP2FA stylesheet loader.' );
}
return WP_2FA_URL . 'dist/css/' . $stylesheet . '.css';
}
/**
* Enqueue scripts for admin.
*
* @return void
*/
function admin_scripts() {
global $pagenow;
// Get page argument from $_GET array.
$page = ( isset( $_GET['page'] ) ) ? \sanitize_text_field( \wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore
if ( ( empty( $page ) || false === strpos( $page, 'wp-2fa' ) ) && 'profile.php' !== $pagenow ) {
return;
}
\wp_enqueue_script(
'wp_2fa_admin',
script_url( 'admin', 'admin' ),
array( 'jquery-ui-widget', 'jquery-ui-core', 'jquery-ui-autocomplete', 'wp_2fa_micro_modals', 'select2' ),
WP_2FA_VERSION,
true
);
\wp_enqueue_script(
'wp_2fa_micro_modals',
script_url( 'micromodal', 'admin' ),
array(),
WP_2FA_VERSION,
true
);
enqueue_select2_scripts();
// Data array.
$data_array = array(
'ajaxURL' => \admin_url( 'admin-ajax.php' ),
'roles' => WP_Helper::get_roles_wp(),
'nonce' => \wp_create_nonce( 'wp-2fa-settings-nonce' ),
'codeValidatedHeading' => \esc_html__( 'Congratulations', 'wp-2fa' ),
'codeValidatedText' => \esc_html__( 'Your account just got more secure', 'wp-2fa' ),
'codeValidatedButton' => \esc_html__( 'Close Wizard & Refresh', 'wp-2fa' ),
'processingText' => \esc_html__( 'Processing Update', 'wp-2fa' ),
'email_sent_success' => \esc_html__( 'Email successfully sent', 'wp-2fa' ),
'email_sent_failure' => \esc_html__( 'Email delivery failed', 'wp-2fa' ),
'invalidEmail' => \esc_html__( 'Please use a valid email address', 'wp-2fa' ),
'license_validation_in_progress' => \esc_html__( 'Validating your license, please wait...', 'wp-2fa' ),
);
\wp_localize_script( 'wp_2fa_admin', 'wp2faData', $data_array );
$role = User_Helper::get_user_role();
$re_login = Settings::get_role_or_default_setting( Re_Login_2FA::RE_LOGIN_SETTINGS_NAME, 'current', $role );
$data_array = array(
'ajaxURL' => \admin_url( 'admin-ajax.php' ),
'nonce' => \wp_create_nonce( 'wp2fa-verify-wizard-page' ),
'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ),
'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ),
'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ),
'backupCodesSent' => \esc_html__( 'Backup codes sent', 'wp-2fa' ),
'reLoginEnabled' => Re_Login_2FA::ENABLED_SETTING_VALUE,
'reLogin' => $re_login,
);
\wp_localize_script( 'wp_2fa_admin', 'wp2faWizardData', $data_array );
}
/**
* Enqueue Select2 jQuery library
*
* @return void
*/
function enqueue_select2_scripts() {
wp_enqueue_style( 'select2', style_url( 'select2.min', 'admin' ), array(), WP_2FA_VERSION );
wp_enqueue_script( 'select2', script_url( 'select2.min', 'admin' ), array( 'jquery' ), WP_2FA_VERSION, false );
}
/**
* Enqueue styles for admin.
*
* @return void
*/
function admin_styles() {
wp_enqueue_style(
'wp_2fa_admin',
style_url( 'admin-style', 'admin' ),
array(),
WP_2FA_VERSION
);
}
/**
* Add async/defer attributes to enqueued scripts that have the specified script_execution flag.
*
* @link https://core.trac.wordpress.org/ticket/12009
* @param string $tag The script tag.
* @param string $handle The script handle.
* @return string
*/
function script_loader_tag( $tag, $handle ) {
$script_execution = wp_scripts()->get_data( $handle, 'script_execution' );
if ( ! $script_execution ) {
return $tag;
}
if ( 'async' !== $script_execution && 'defer' !== $script_execution ) {
return $tag;
}
// Abort adding async/defer for scripts that have this script as a dependency. _doing_it_wrong()?
foreach ( wp_scripts()->registered as $script ) {
if ( in_array( $handle, $script->deps, true ) ) {
return $tag;
}
}
// Add the attribute if it hasn't already been added.
if ( ! preg_match( ":\s$script_execution(=|>|\s):", $tag ) ) {
$tag = preg_replace( ':(?=></script>):', " $script_execution", $tag, 1 );
}
return $tag;
}
/**
* Generates random string used to salt the key
*
* @return string
*
* @since 2.3.0
*/
function wp_salt(): string {
return WP2FA::get_secret_key();
}
includes/functions/login-header.php 0000644 00000015454 15075513060 0013444 0 ustar 00 <?php
/**
* Output the login page header.
*
* @param string $title Optional. WordPress login Page title to display in the `<title>` element.
* Default 'Log In'.
* @param string $message Optional. Message to display in header. Default empty.
* @param WP_Error $wp_error Optional. The error to pass. Default is a WP_Error instance.
*/
function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
global $error, $interim_login, $action;
// Don't index any of these forms.
add_action( 'login_head', 'wp_no_robots' );
add_action( 'login_head', 'wp_login_viewport_meta' );
if ( ! is_wp_error( $wp_error ) ) {
$wp_error = new WP_Error();
}
// Shake it!
$shake_error_codes = array( 'empty_password', 'empty_email', 'invalid_email', 'invalidcombo', 'empty_username', 'invalid_username', 'incorrect_password' );
/**
* Filters the error codes array for shaking the login form.
*
* @since 3.0.0
*
* @param array $shake_error_codes Error codes that shake the login form.
*/
$shake_error_codes = apply_filters( 'shake_error_codes', $shake_error_codes );
if ( $shake_error_codes && $wp_error->get_error_code() && in_array( $wp_error->get_error_code(), $shake_error_codes ) ) {
add_action( 'login_head', 'wp_shake_js', 12 );
}
$login_title = get_bloginfo( 'name', 'display' );
/* translators: Login screen title. 1: Login screen name, 2: Network or site name */
$login_title = sprintf( __( '%1$s ‹ %2$s — WordPress' ), $title, $login_title );
/**
* Filters the title tag content for login page.
*
* @since 4.9.0
*
* @param string $login_title The page title, with extra context added.
* @param string $title The original page title.
*/
$login_title = apply_filters( 'login_title', $login_title, $title );
?><!DOCTYPE html>
<!--[if IE 8]>
<html xmlns="http://www.w3.org/1999/xhtml" class="ie8" <?php language_attributes(); ?>>
<![endif]-->
<!--[if !(IE 8) ]><!-->
<html xmlns="http://www.w3.org/1999/xhtml" <?php language_attributes(); ?>>
<!--<![endif]-->
<head>
<meta http-equiv="Content-Type" content="<?php bloginfo( 'html_type' ); ?>; charset=<?php bloginfo( 'charset' ); ?>" />
<title><?php echo $login_title; ?></title>
<?php
wp_enqueue_style( 'login' );
/*
* Remove all stored post data on logging out.
* This could be added by add_action('login_head'...) like wp_shake_js(),
* but maybe better if it's not removable by plugins
*/
if ( 'loggedout' == $wp_error->get_error_code() ) {
?>
<script>if("sessionStorage" in window){try{for(var key in sessionStorage){if(key.indexOf("wp-autosave-")!=-1){sessionStorage.removeItem(key)}}}catch(e){}};</script>
<?php
}
/**
* Enqueue scripts and styles for the login page.
*
* @since 3.1.0
*/
do_action( 'login_enqueue_scripts' );
/**
* Fires in the login page header after scripts are enqueued.
*
* @since 2.1.0
*/
do_action( 'login_head' );
if ( \WP2FA\Admin\Helpers\WP_Helper::is_multisite() ) {
$login_header_url = network_home_url();
$login_header_title = get_network()->site_name;
} else {
$login_header_url = __( 'https://wordpress.org/' );
$login_header_title = __( 'Powered by WordPress' );
}
/**
* Filters link URL of the header logo above login form.
*
* @since 2.1.0
*
* @param string $login_header_url Login header logo URL.
*/
$login_header_url = apply_filters( 'login_headerurl', $login_header_url );
/**
* Filters the title attribute of the header logo above login form.
*
* @since 2.1.0
*
* @param string $login_header_title Login header logo title attribute.
*/
$login_header_title = apply_filters( 'login_headertitle', $login_header_title );
/*
* To match the URL/title set above, Multisite sites have the blog name,
* while single sites get the header title.
*/
if ( \WP2FA\Admin\Helpers\WP_Helper::is_multisite() ) {
$login_header_text = get_bloginfo( 'name', 'display' );
} else {
$login_header_text = $login_header_title;
}
$classes = array( 'login-action-' . $action, 'wp-core-ui' );
if ( is_rtl() ) {
$classes[] = 'rtl';
}
if ( $interim_login ) {
$classes[] = 'interim-login';
?>
<style type="text/css">html{background-color: transparent;}</style>
<?php
if ( 'success' === $interim_login ) {
$classes[] = 'interim-login-success';
}
}
$classes[] = ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );
/**
* Filters the login page body classes.
*
* @since 3.5.0
*
* @param array $classes An array of body classes.
* @param string $action The action that brought the visitor to the login page.
*/
$classes = apply_filters( 'login_body_class', $classes, $action );
?>
</head>
<body class="login <?php echo esc_attr( implode( ' ', $classes ) ); ?>">
<?php
/**
* Fires in the login page header after the body tag is opened.
*
* @since 4.6.0
*/
do_action( 'login_header' );
?>
<div id="login">
<h1><a href="<?php echo esc_url( $login_header_url ); ?>" title="<?php echo esc_attr( $login_header_title ); ?>" tabindex="-1"><?php echo $login_header_text; ?></a></h1>
<?php
unset( $login_header_url, $login_header_title );
/**
* Filters the message to display above the login form.
*
* @since 2.1.0
*
* @param string $message Login message text.
*/
$message = apply_filters( 'login_message', $message );
if ( ! empty( $message ) ) {
echo $message . "\n";
}
// In case a plugin uses $error rather than the $wp_errors object.
if ( ! empty( $error ) ) {
$wp_error->add( 'error', $error );
unset( $error );
}
if ( $wp_error->get_error_code() ) {
$errors = '';
$messages = '';
foreach ( $wp_error->get_error_codes() as $code ) {
$severity = $wp_error->get_error_data( $code );
foreach ( $wp_error->get_error_messages( $code ) as $error_message ) {
if ( 'message' == $severity ) {
$messages .= ' ' . $error_message . "<br />\n";
} else {
$errors .= ' ' . $error_message . "<br />\n";
}
}
}
if ( ! empty( $errors ) ) {
/**
* Filters the error messages displayed above the login form.
*
* @since 2.1.0
*
* @param string $errors Login error message.
*/
echo '<div id="login_error">' . apply_filters( 'login_errors', $errors ) . "</div>\n";
}
if ( ! empty( $messages ) ) {
/**
* Filters instructional messages displayed above the login form.
*
* @since 2.5.0
*
* @param string $messages Login messages.
*/
echo '<p class="message">' . apply_filters( 'login_messages', $messages ) . "</p>\n";
}
}
} // End of login_header().
function wp_login_viewport_meta() {
?>
<meta name="viewport" content="width=device-width" />
<?php
}
includes/classes/Utils/class-user-utils.php 0000644 00000025460 15075513060 0015050 0 ustar 00 <?php
/**
* Responsible for different user's manipulations.
*
* @package wp2fa
* @subpackage user-utils
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Utils;
use WP2FA\WP2FA;
use WP2FA\Methods\Backup_Codes;
use WP2FA\Admin\Helpers\User_Helper;
if ( ! class_exists( '\WP2FA\Utils\User_Utils' ) ) {
/**
* Utility class for creating modal popup markup.
*
* @package WP2FA\Utils
*
* @since 1.4.2
*/
class User_Utils {
/**
* Holds map with human readable 2FA statuses.
*
* @var array
*
* @since 2.2.0
*/
private static $statuses;
/**
* Determines the proper 2FA status of the given user.
*
* @param \WP_User $user - The user to check.
*
* @return array
*
* @since 2.2.0
*/
public static function determine_user_2fa_status( $user ) {
// Get current user, we going to need this regardless.
$current_user = wp_get_current_user();
// Bail if we still dont have an object.
if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
return array();
}
$roles = (array) $user->roles;
// Grab grace period UNIX time.
$grace_period_expired = User_Helper::get_grace_period( $user );
$is_user_excluded = User_Helper::is_excluded( $user->ID );
$is_user_enforced = User_Helper::is_enforced( $user->ID );
$is_user_locked = User_Helper::is_user_locked( $user->ID );
$user_last_login = User_Helper::get_login_date_for_user( $user->ID );
// First lets see if the user already has a token.
$enabled_methods = User_Helper::get_enabled_method_for_user( $user );
$no_enforced_methods = false;
if ( 'do-not-enforce' === WP2FA::get_wp2fa_setting( 'enforcement-policy' ) ) {
$no_enforced_methods = true;
}
$user_type = array();
// Order is important here - for speed optimizations see self::extract_statuses() function of that class - we probably need to redo the whole thing.
if ( $no_enforced_methods && ! empty( $enabled_methods ) ) {
$user_type[] = 'no_required_has_enabled';
}
if ( $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded ) {
if ( empty( $user_last_login ) ) {
$user_type[] = User_Helper::USER_UNDETERMINED_STATUS;
} else {
$user_type[] = 'no_required_not_enabled';
}
}
if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && $is_user_enforced ) {
$user_type[] = 'user_needs_to_setup_2fa';
}
if ( ! $no_enforced_methods && empty( $enabled_methods ) && ! $is_user_excluded && ! $is_user_enforced ) {
if ( empty( $user_last_login ) ) {
$user_type[] = User_Helper::USER_UNDETERMINED_STATUS;
} else {
$user_type[] = 'no_required_not_enabled';
}
}
if ( $is_user_excluded ) {
$user_type[] = 'user_is_excluded';
}
if ( $is_user_locked ) {
$user_type[] = 'user_is_locked';
}
if ( ! empty( $enabled_methods ) ) {
$user_type[] = 'has_enabled_methods';
}
$codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
if ( 0 === $codes_remaining ) {
$user_type[] = 'user_needs_to_setup_backup_codes';
}
if ( empty( $roles ) ) {
$user_type[] = 'orphan_user'; // User has no role.
}
if ( \current_user_can( 'manage_options' ) ) {
$user_type[] = 'can_manage_options';
}
if ( \current_user_can( 'read' ) ) {
$user_type[] = 'can_read';
}
if ( $grace_period_expired ) {
$user_type[] = 'grace_has_expired';
}
if ( $current_user->ID === $user->ID ) {
$user_type[] = 'viewing_own_profile';
}
/*
* Gives the ability to alter the user types for the user.
*
* @param string $user_type - Type of the user.
* @param \WP_User $user - The WP user.
*
* @since 2.0.0
*/
return \apply_filters( WP_2FA_PREFIX . 'additional_user_types', $user_type, $user );
}
/**
* Checks is all values exist in given array.
*
* @param array $needles - Which values to check.
* @param array $haystack - The array to check against.
*
* @return bool
*
* @since 2.2.0
*/
public static function in_array_all( $needles, $haystack ) {
return empty( array_diff( $needles, $haystack ) );
}
/**
* Check if role is not in given array of roles.
*
* @param array $roles - All roles.
* @param array $user_roles - The User roles.
*
* @return bool
*
* @since 2.2.0
*/
public static function role_is_not( $roles, $user_roles ) {
if (
empty(
array_intersect(
$roles,
$user_roles
)
)
) {
return true;
}
return false;
}
/**
* Return all users, either by using a direct query or get_users.
*
* @param string $method Method to use.
* @param array $users_args Query arguments.
*
* @return mixed Array of IDs/Object of Users.
*
* @since 2.2.0
*/
public static function get_all_users_data( $method, $users_args ) {
if ( 'get_users' === $method ) {
return get_users( $users_args );
}
// method is "query", let's build the SQL query ourselves.
global $wpdb;
$batch_size = isset( $users_args['batch_size'] ) ? $users_args['batch_size'] : false;
$offset = isset( $users_args['count'] ) ? $users_args['count'] * $batch_size : false;
// Default.
$select = 'SELECT ID, user_login FROM ' . $wpdb->users . '';
// If we want to grab users with a specific role.
if ( isset( $users_args['role__in'] ) && ! empty( $users_args['role__in'] ) ) {
$roles = $users_args['role__in'];
$select = '
SELECT ID, user_login
FROM ' . $wpdb->users . ' u INNER JOIN ' . $wpdb->usermeta . ' um
ON u.ID = um.user_id
WHERE um.meta_key LIKE \'' . $wpdb->base_prefix . '%capabilities\'' . // phpcs:ignore
' AND (
';
$i = 1;
foreach ( $roles as $role ) {
$select .= ' um.meta_value LIKE \'%"' . $role . '"%\' ';
if ( $i < count( $roles ) ) {
$select .= ' OR ';
}
++$i;
}
$select .= ' ) ';
$excluded_users = ( ! empty( $users_args['excluded_users'] ) ) ? $users_args['excluded_users'] : array();
$excluded_users = array_map(
function ( $excluded_user ) {
return '"' . $excluded_user . '"';
},
$excluded_users
);
if ( ! empty( $excluded_users ) ) {
$select .= '
AND user_login NOT IN ( ' . implode( ',', $excluded_users ) . ' )
';
}
$skip_existing_2fa_users = ( ! empty( $users_args['skip_existing_2fa_users'] ) ) ? $users_args['skip_existing_2fa_users'] : false;
if ( $skip_existing_2fa_users ) {
$select .= '
AND u.ID NOT IN (
SELECT DISTINCT user_id FROM ' . $wpdb->usermeta . ' WHERE meta_key = \'wp_2fa_enabled_methods\'
)
';
}
}
if ( $batch_size ) {
$select .= ' LIMIT ' . $batch_size . ' OFFSET ' . $offset . '';
}
return $wpdb->get_results( $select ); // phpcs:ignore
}
/**
* Collects all the users with 2FA meta data.
*
* @param array $users_args - Arguments.
*
* @return string
*
* @since 2.2.0
*/
public static function get_all_user_ids_who_have_wp_2fa_metadata_present( $users_args ) {
global $wpdb;
$batch_size = isset( $users_args['batch_size'] ) ? $users_args['batch_size'] : false;
$offset = isset( $users_args['count'] ) ? $users_args['count'] * $batch_size : false;
$select = '
SELECT ID FROM ' . $wpdb->users . '
INNER JOIN ' . $wpdb->usermeta . ' ON ' . $wpdb->users . '.ID = ' . $wpdb->usermeta . '.user_id
WHERE ' . $wpdb->usermeta . '.meta_key LIKE \'wp_2fa_%\'
';
if ( $batch_size ) {
$select .= '
LIMIT ' . $batch_size . ' OFFSET ' . $offset . '
';
}
$users = $wpdb->get_results( $select ); // phpcs:ignore
$users = array_map(
function ( $user ) {
return (int) $user->ID;
},
$users
);
$users = implode( ',', $users );
return $users;
}
/**
* Retrieve string of comma separated IDs.
*
* @param string $method Method to use.
* @param array $users_args Query arguments.
*
* @return string List of IDs.
*
* @since 2.2.0
*/
public static function get_all_user_ids( $method, $users_args ) {
$user_data = self::get_all_users_data( $method, $users_args );
$users = array_map(
function ( $user ) {
return (int) $user->ID;
},
$user_data
);
return implode( ',', $users );
}
/**
* Retrieve array if user IDs and login names.
*
* @param string $method Method to use.
* @param array $users_args Query arguments.
*
* @return array User details.
*
* @since 2.2.0
*/
public static function get_all_user_ids_and_login_names( $method, $users_args ) {
$user_data = self::get_all_users_data( $method, $users_args );
$users = array_map(
function ( $user ) {
$user_item['ID'] = (int) $user->ID;
$user_item['user_login'] = $user->user_login;
return $user_item;
},
$user_data
);
return $users;
}
/**
* Returns the array with human readable statuses of the WP 2FA.
*
* @since 1.6
*
* @return array
*/
public static function get_human_readable_user_statuses() {
if ( null === self::$statuses ) {
self::$statuses =
array(
'has_enabled_methods' => __( 'Configured', 'wp-2fa' ),
'user_needs_to_setup_2fa' => __( 'Required but not configured', 'wp-2fa' ),
'no_required_has_enabled' => __( 'Configured (but not required)', 'wp-2fa' ),
'no_required_not_enabled' => __( 'Not required & not configured', 'wp-2fa' ),
'user_is_excluded' => __( 'Not allowed', 'wp-2fa' ),
'user_is_locked' => __( 'Locked', 'wp-2fa' ),
User_Helper::USER_UNDETERMINED_STATUS => __( 'User has not logged in yet, 2FA status is unknown', 'wp-2fa' ),
);
}
return self::$statuses;
}
/**
* Gets the user types extracted with @see User_Utils::determine_user_2fa_status,
* checks values and generates human readable 2FA status text.
*
* @param array $user_types - The types of the user.
*
* @return array An array with the id and label elements of user 2FA status. Empty in case there is not match.
*
* @since 1.7.0 Changed the function to return the id and label of the first match it finds instead of concatenated labels of all matched statuses.
*/
public static function extract_statuses( array $user_types ) {
if ( null === self::$statuses ) {
self::get_human_readable_user_statuses();
}
if ( empty( $user_types ) ) {
return array();
}
$key_to_search = reset( $user_types );
if ( isset( self::$statuses[ $key_to_search ] ) ) {
return array(
'id' => $key_to_search,
'label' => self::$statuses[ $key_to_search ],
);
}
return array();
}
}
}
includes/classes/Utils/index.php 0000644 00000000046 15075513060 0012731 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Utils/class-debugging.php 0000644 00000011403 15075513060 0014657 0 ustar 00 <?php
/**
* Responsible for logging.
*
* @package wp2fa
* @subpackage utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
* @since 1.4.2
*/
declare(strict_types=1);
namespace WP2FA\Utils;
use PhpParser\Node\Stmt\Static_;
if ( ! class_exists( '\WP2FA\Utils\Debugging' ) ) {
/**
* Utility class for creating modal popup markup.
*
* @package WP2FA\Utils
*
* @since 1.4.2
*/
class Debugging {
/**
* Local cache for the logging dir so that it doesn't need to be repopulated each time get_logging_dir_path is called.
*
* @var string
*
* @since 1.4.2
*/
private static $logging_dir_path = '';
/**
* Retrieve the logging status
*
* @return boolean
*
* @since 1.4.2
*/
private static function is_logging_enabled() {
/**
* Enables / Disables the logging for the plugin.
*
* @param bool $disabled - Default logging for the plugin.
*/
return apply_filters( WP_2FA_PREFIX . 'logging_enabled', false );
}
/**
* Logs the given message
*
* @param string $message - The message to log.
*
* @return void
*
* @since 1.4.2
*/
public static function log( $message ) {
if ( self::is_logging_enabled() ) {
self::write_to_log( self::get_log_timestamp() . "\n" . $message . "\n" . __( 'Current memory usage: ', 'wp-2fa' ) . memory_get_usage( true ) . "\n" );
}
}
/**
* Retrieves the path to the log file
*
* @return string
*
* @since 1.4.2
*/
private static function get_logging_dir_path() {
if ( strlen( self::$logging_dir_path ) === 0 ) {
$uploads_dir = wp_upload_dir( null, false );
self::$logging_dir_path = trailingslashit( trailingslashit( $uploads_dir['basedir'] ) . WP_2FA_LOGS_DIR );
}
return self::$logging_dir_path;
}
/**
* Write data to log file.
*
* @param string $data - Data to write to file.
* @param bool $override - Set to true if overriding the file.
*
* @return bool
*
* @since 1.4.2
*/
private static function write_to_log( $data, $override = false ) {
$logging_dir_path = self::get_logging_dir_path();
if ( ! is_dir( $logging_dir_path ) ) {
self::create_index_file();
self::create_htaccess_file();
}
$log_file_name = gmdate( 'Y-m-d' );
return self::write_to_file( 'wp-2fa-debug-' . $log_file_name . '-' . self::get_random_file_string_addon() . '.log', $data, $override );
}
/**
* Create an index.php file, if none exists, in order to
* avoid directory listing in the specified directory.
*
* @return bool
*
* @since 1.4.2
*/
private static function create_index_file() {
return self::write_to_file( 'index.php', '<?php // Silence is golden' );
}
/**
* Create an .htaccess file, if none exists, in order to
* block access to directory listing in the specified directory.
*
* @return bool
*
* @since 1.4.2
*/
private static function create_htaccess_file() {
return self::write_to_file( '.htaccess', 'Deny from all' );
}
/**
* Write data to log file in the uploads directory.
*
* @param string $filename - File name.
* @param string $content - Contents of the file.
* @param bool $override - (Optional) True if overriding file contents.
*
* @return bool
*
* @since 1.4.2
*/
private static function write_to_file( $filename, $content, $override = false ) {
global $wp_filesystem;
require_once ABSPATH . 'wp-admin/includes/file.php';
WP_Filesystem();
$logging_dir = self::get_logging_dir_path();
$result = false;
if ( ! is_dir( $logging_dir ) ) {
if ( false === wp_mkdir_p( $logging_dir ) ) {
return false;
}
}
$filepath = $logging_dir . $filename;
if ( ! $wp_filesystem->exists( $filepath ) || $override ) {
$result = $wp_filesystem->put_contents( $filepath, $content );
} else {
$existing_content = $wp_filesystem->get_contents( $filepath );
$result = $wp_filesystem->put_contents( $filepath, $existing_content . $content );
}
return $result;
}
/**
* Returns the timestamp for log files.
*
* @return string
*
* @since 1.4.2
*/
private static function get_log_timestamp() {
return '[' . gmdate( 'd-M-Y H:i:s' ) . ' UTC]';
}
/**
* Generates a short random string which is used to generate log file name.
*
* @return string
*
* @since 2.8.0
*/
private static function get_random_file_string_addon(): string {
$rnd_string = Settings_Utils::get_option( 'debug_name', false );
if ( ! $rnd_string ) {
$rnd_string = (string) \wp_generate_password( 20, false, false );
Settings_Utils::update_option( 'debug_name', $rnd_string );
}
return $rnd_string;
}
}
}
includes/classes/Utils/class-date-time-utils.php 0000644 00000004160 15075513060 0015735 0 ustar 00 <?php
/**
* Responsible for date / time manipulation.
*
* @package wp2fa
* @subpackage utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Utils;
use WP2FA\WP2FA;
if ( ! class_exists( '\WP2FA\Utils\Date_Time_Utils' ) ) {
/**
* Utility class for date and time manipulation, format conversion and so on.
*
* @package WP2FA\Utils
* @since 1.4.2
*/
class Date_Time_Utils {
/**
* Formats the date string
*
* @param string|null $grace_policy Grace policy value.
* @param int $grace_expiry Expiration time as unix based timestamp.
*
* @return string Translated grace period expiration string.
*/
public static function format_grace_period_expiration_string( $grace_policy = null, $grace_expiry = - 1 ) {
if ( null === $grace_policy ) {
$grace_policy = WP2FA::get_wp2fa_setting( 'grace-policy' );
}
if ( 'no-grace-period' === $grace_policy ) {
return \esc_html__( 'no grace period', 'wp-2fa' );
}
if ( -1 === $grace_expiry ) {
if ( 'use-grace-period' === $grace_policy ) {
$grace_period = WP2FA::get_wp2fa_setting( 'grace-period' );
$grace_period_denominator = WP2FA::get_wp2fa_setting( 'grace-period-denominator' );
$grace_period_string = $grace_period . ' ' . $grace_period_denominator;
$grace_expiry = (int) strtotime( $grace_period_string );
} else {
// this will probably never be reached, leaving it here for now just in case.
$grace_expiry = time();
}
}
$expiration_date_time = implode(
' ',
array(
// Purposefully not using the SettingsUtil class as we don't want this prefixed.
date_i18n( get_option( 'date_format' ), $grace_expiry ),
date_i18n( get_option( 'time_format' ), $grace_expiry ),
)
);
/* translators: Grace period expiration label. %s: Date and time formatted using WordPress date and time formats. */
return sprintf( \esc_html__( 'before %s', 'wp-2fa' ), $expiration_date_time );
}
}
}
includes/classes/Utils/class-settings-utils.php 0000644 00000006640 15075513060 0015731 0 ustar 00 <?php
/**
* Responsible for various settings manipulations.
*
* @package wp2fa
* @subpackage utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Utils;
use WP2FA\Admin\Helpers\WP_Helper;
if ( ! class_exists( '\WP2FA\Utils\Settings_Utils' ) ) {
/**
* Utility class handling settings CRUD.
*
* @package WP2FA\Utils
*
* @since 1.7.0
*/
class Settings_Utils {
/**
* Creates a hash based on the passed settings array.
*
* @param array $settings - Settings array.
*
* @return string
*/
public static function create_settings_hash( array $settings ): string {
return md5( json_encode( $settings ) ); // phpcs:ignore
}
/**
* Returns an option by given name
*
* @param string $setting_name - The name of the option.
* @param mixed $default_value - The default value if there is no one stored.
*
* @return mixed
*
* @since 2.0.0
*/
public static function get_option( $setting_name, $default_value = false ) {
$prefixed_setting_name = self::setting_prefixer( $setting_name );
return ( WP_Helper::is_multisite() ) ? get_network_option( null, $prefixed_setting_name, $default_value ) : get_option( $prefixed_setting_name, $default_value );
}
/**
* Updates an option by a given name with a given value
*
* @param string $setting_name - The name of the setting to update.
* @param mixed $new_value - The value to be stored.
*
* @return mixed
*
* @since 2.0.0
*/
public static function update_option( $setting_name, $new_value ) {
$prefixed_setting_name = self::setting_prefixer( $setting_name );
return ( WP_Helper::is_multisite() ) ? update_network_option( null, $prefixed_setting_name, $new_value ) : update_option( $prefixed_setting_name, $new_value, true );
}
/**
* Deletes an option by a given name
*
* @param string $setting_name - The name of the option to delete.
*
* @return mixed
*
* @since 2.0.0
*/
public static function delete_option( $setting_name ) {
$prefixed_setting_name = self::setting_prefixer( $setting_name );
return ( WP_Helper::is_multisite() ) ? delete_network_option( null, $prefixed_setting_name ) : delete_option( $prefixed_setting_name );
}
/**
* Created a prefixed setting name from supplied string.
*
* @param string $setting_name - The name of the setting.
*
* @return string
*/
private static function setting_prefixer( $setting_name ) {
// Ensure we have not already been passed a prefixed setting name.
return ( strpos( $setting_name, 'wp_2fa_' ) === 0 ) ? $setting_name : WP_2FA_PREFIX . $setting_name;
}
/**
* Converts a string (e.g. 'yes' or 'no') to a bool.
*
* @since 2.0.0
* @param string $string String to convert.
* @return bool
*/
public static function string_to_bool( $string ) {
return is_bool( $string ) ? $string : ( 'yes' === $string || 1 === $string || 'true' === $string || '1' === $string || 'on' === $string || 'enable' === $string );
}
/**
* Converts a bool to a 'yes' or 'no'.
*
* @since 2.0.0
* @param bool $bool String to convert.
* @return string
*/
public static function bool_to_string( $bool ) {
if ( ! is_bool( $bool ) ) {
$bool = self::string_to_bool( $bool );
}
return true === $bool ? 'yes' : 'no';
}
}
}
includes/classes/Utils/class-generate-modal.php 0000644 00000005433 15075513060 0015616 0 ustar 00 <?php
/**
* Responsible for modal dialogs generation.
*
* @package wp2fa
* @subpackage utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Utils;
use WP2FA\WP2FA;
if ( ! class_exists( '\WP2FA\Utils\Generate_Modal' ) ) {
/**
* Utility class for creating modal popup markup.
*
* @package WP2FA\Utils
*
* @since 1.4.2
*/
class Generate_Modal {
/**
* General modals based on given args.
*
* @param string $modal_id Unique ID for the modal.
* @param string $modal_title (Optional) Modal title.
* @param string $modal_content The HTML content we want to show in the modal.
* @param array $modal_footer_buttons The HTML content we want to show at the footer of the modal, usually buttons.
* @param string $should_modal_autoopen (Optional) if anything is passed we will open the modal automatically.
* @param string $max_width (Optional) Max possible width of modal.
*/
public static function generate_modal( $modal_id, $modal_title, $modal_content, $modal_footer_buttons = array(), $should_modal_autoopen = '', $max_width = '' ) {
$buttons = '';
$modal = '';
$title = ( ! empty( $modal_title ) ) ? '<header class="modal__header"><h4 class="modal__title" id="modal-' . \esc_attr( $modal_id ) . '-title">' . $modal_title . '</h4></header>' : false;
if ( ! empty( $modal_footer_buttons ) ) {
foreach ( $modal_footer_buttons as $button_markup ) {
$buttons .= $button_markup;
}
}
$styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
if ( ! empty( $should_modal_autoopen ) ) {
$modal_class = 'wp2fa-modal micromodal-slide is-open ' . $styling_class;
$hidden = 'false';
} else {
$modal_class = 'wp2fa-modal micromodal-slide ' . $styling_class;
$hidden = 'true';
}
$max_width_styles = ( ! empty( $max_width ) ) ? 'style="max-width:' . \esc_attr( $max_width ) . '; min-width: 0;"' : false;
$modal = '
<div class="' . $modal_class . '" id="' . \esc_attr( $modal_id ) . '" aria-hidden="' . \esc_attr( $hidden ) . '">
<div class="modal__overlay" tabindex="-1">
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-' . \esc_attr( $modal_id ) . '-title" ' . $max_width_styles . '>
' . $title . '
<main class="modal__content wp2fa-form-styles" id="modal-' . \esc_attr( $modal_id ) . '-content">
' . wpautop( $modal_content ) . '
</main>
<footer class="modal__footer">
' . $buttons . '
</footer>
</div>
</div>
</div>
';
return $modal;
}
}
}
includes/classes/Utils/class-migration.php 0000644 00000022000 15075513060 0014710 0 ustar 00 <?php
/**
* Responsible for plugin updates.
*
* @package wp2fa
* @subpackage utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Utils;
use WP2FA\Utils\Abstract_Migration;
use WP2FA\Utils\User_Utils;
use WP2FA\Utils\Settings_Utils;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* Migration class
*/
if ( ! class_exists( '\WP2FA\Utils\Migration' ) ) {
/**
* Put all you migration methods here
*
* @package WP2FA\Utils
* @since 1.6
*/
class Migration extends Abstract_Migration {
/**
* The name of the option from which we should extract version
* Note: version is expected in version format - 1.0.0; 1; 1.0; 1.0.0.0
* Note: only numbers will be processed
*
* @var string
*
* @since 1.6.0
*/
protected static $version_option_name = WP_2FA_PREFIX . 'plugin_version';
/**
* The constant name where the plugin version is stored
* Note: version is expected in version format - 1.0.0; 1; 1.0; 1.0.0.0
* Note: only numbers will be processed
*
* @var string
*
* @since 1.6.0
*/
protected static $const_name_of_plugin_version = 'WP_2FA_VERSION';
/**
* The name of the plugin settings
*
* @var string
*/
private static $plugin_settings_name = WP_2FA_SETTINGS_NAME;
/**
* The name of the plugin policy settings
*
* @var string
*/
private static $plugin_policy_name = WP_2FA_POLICY_SETTINGS_NAME;
/**
* The name of the plugin white label settings
*
* @var string
*/
private static $plugin_white_label_name = WP_2FA_WHITE_LABEL_SETTINGS_NAME;
/**
* The name of the plugin email settings
*
* @var string
*/
private static $plugin_email_settings_name = WP_2FA_EMAIL_SETTINGS_NAME;
/**
* Migration for version upto 1.6.0
*
* @return void
* @since 1.6.0
*/
protected static function migrate_up_to_160() {
$settings = self::get_settings( self::$plugin_settings_name );
if ( ! is_array( $settings ) ) {
return;
}
$needs_update = false;
$settings_to_convert = array( 'enforced_roles', 'enforced_users', 'excluded_users', 'excluded_roles' );
foreach ( $settings_to_convert as $setting_name ) {
if ( array_key_exists( $setting_name, $settings ) && ! is_array( $settings[ $setting_name ] ) ) {
$settings[ $setting_name ] = array_filter(
explode( ',', $settings[ $setting_name ] )
);
$needs_update = true;
}
}
if ( ! isset( $settings['backup_codes_enabled'] ) ) {
$settings['backup_codes_enabled'] = 'yes';
$needs_update = true;
}
if ( $needs_update ) {
// Update settings.
self::set_settings( self::$plugin_settings_name, $settings );
}
}
/**
* Migration for version upto 1.6.2
*
* @return void
* @since 1.6.2
*/
protected static function migrate_up_to_162() {
$settings = self::get_settings( self::$plugin_settings_name );
if ( ! is_array( $settings ) ) {
return;
}
$needs_update = false;
$settings_to_convert = array( 'excluded_sites' );
foreach ( $settings_to_convert as $setting_name ) {
if ( array_key_exists( $setting_name, $settings ) && ! is_array( $settings[ $setting_name ] ) ) {
$original_settings_split = array_filter(
explode( ',', $settings[ $setting_name ] )
);
$settings[ $setting_name ] = array();
foreach ( $original_settings_split as $value ) {
$settings[ $setting_name ][] = mb_substr( $value, mb_strrpos( $value, ':' ) + 1 );
}
$needs_update = true;
}
}
self::migrate_up_to_160();
if ( $needs_update ) {
// Update settings.
self::set_settings( self::$plugin_settings_name, $settings );
}
}
/**
* Migration for version upto 1.5.0
*
* @return void
*/
protected static function migrate_up_to_150() {
$settings = self::get_settings( self::$plugin_settings_name );
if ( is_array( $settings ) && array_key_exists( 'enforcment-policy', $settings ) ) {
// Correct setting name.
$settings['enforcement-policy'] = $settings['enforcment-policy'];
// Remove old setting.
unset( $settings['enforcment-policy'] );
// Update settings.
self::set_settings( self::$plugin_settings_name, $settings );
}
}
/**
* Migration for version upto 1.7.0
*
* @return void
*/
protected static function migrate_up_to_170() {
$settings = self::get_settings( self::$plugin_settings_name );
if ( is_array( $settings ) && array_key_exists( 'notify_users', $settings ) ) {
// Remove old setting.
unset( $settings['notify_users'] );
// Update settings.
self::set_settings( self::$plugin_settings_name, $settings );
}
$email_settings = self::get_settings( self::$plugin_email_settings_name );
$items_to_remove = array( 'send_enforced_email', 'enforced_email_subject', 'enforced_email_body' );
if ( is_array( $email_settings ) && User_Utils::in_array_all( $items_to_remove, $email_settings ) ) {
foreach ( $items_to_remove as $item ) {
if ( isset( $email_settings[ $item ] ) ) {
unset( $email_settings[ $item ] );
}
}
// Update settings.
self::set_settings( self::$plugin_email_settings_name, $email_settings );
}
}
/**
* Migration for version upto 2.0.0
* Separates the current settings into 3 different types of settings:
* - Policy
* - General
* - White label
*
* @return void
*/
protected static function migrate_up_to_200() {
$settings = self::get_settings( self::$plugin_settings_name );
if ( is_array( $settings ) ) {
$new_settings_array = array_flip(
array(
'enable_grace_cron',
'limit_access',
'delete_data_upon_uninstall',
'enable_destroy_session',
)
);
$new_white_label_array = array_flip(
array(
'default-text-code-page',
)
);
$settings_array = array_intersect_key(
$settings,
$new_settings_array
);
$settings = array_diff_key( $settings, $new_settings_array );
self::set_settings( self::$plugin_settings_name, $settings_array );
$white_label_settings = array_intersect_key(
$settings,
$new_white_label_array
);
$settings = array_diff_key( $settings, $new_white_label_array );
self::set_settings( self::$plugin_white_label_name, $white_label_settings );
self::set_settings( self::$plugin_policy_name, $settings );
}
}
/**
* Migration for version upto 2.2.0
*
* @return void
*/
protected static function migrate_up_to_220() {
global $wpdb;
$new_prefix = 'wp_2fa_trusted_device_';
$old_prefix = 'wp2fa_trusted_device_';
delete_transient( 'wp_2fa_config_file_hash' );
$wpdb->query(
$wpdb->prepare(
"
UPDATE $wpdb->usermeta
SET meta_key = REPLACE( meta_key, %s, %s )
WHERE meta_key LIKE %s
",
array(
$old_prefix,
$new_prefix,
$old_prefix . '%',
)
)
);
}
/**
* Migration for version upto 2.3.0
*
* @return void
*/
protected static function migrate_up_to_230() {
$version = self::get_settings( self::$version_option_name );
if ( $version && version_compare( $version, '2.2.1', '<=' ) ) {
$settings = self::get_settings( self::$plugin_white_label_name );
if ( isset( $settings['enable_wizard_styling'] ) ) {
$settings['enable_wizard_styling'] = false;
} else {
$settings = array();
$settings['enable_wizard_styling'] = false;
}
self::set_settings( self::$plugin_white_label_name, $settings );
}
}
/**
* Migration for version upto 2.4.0
*
* @return void
*/
protected static function migrate_up_to_240() {
\delete_transient( 'wp_2fa_config_file_hash' );
if ( \wp_next_scheduled( 'wp_2fa_check_grace_period_status' ) ) {
\wp_clear_scheduled_hook( 'wp_2fa_check_grace_period_status' );
}
}
/**
* Migration for version upto 2.6.2
*
* @return void
*/
protected static function migrate_up_to_262() {
self::migrate_up_to_240();
}
/**
* Migration for version upto 2.6.3
*
* @return void
*/
protected static function migrate_up_to_263() {
self::migrate_up_to_240();
}
/**
* Migration for version upto 2.8.0
*
* @return void
*
* @since 2.8.0
*/
protected static function migrate_up_to_280() {
self::migrate_up_to_240();
}
/**
* Returns the plugin settings by a given setting type
*
* @param mixed $setting_name - The setting which needs to be extracted.
*
* @return mixed
*/
private static function get_settings( $setting_name ) {
return Settings_Utils::get_option( $setting_name );
}
/**
* Updates the plugin settings
*
* @param mixed $setting_name - The setting which needs to be updated.
* @param mixed $settings - The settings values.
*
* @return void
*/
private static function set_settings( $setting_name, $settings ) {
Settings_Utils::update_option( $setting_name, $settings );
}
}
}
includes/classes/Utils/class-white-label.php 0000644 00000037672 15075513060 0015141 0 ustar 00 <?php
/**
* Responsible for white labeling functionality.
*
* @package wp2fa
* @subpackage white-label
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Utils;
if ( ! class_exists( '\WP2FA\Utils\White_Label' ) ) {
/**
* Utility class for white labeling.
*
* @package WP2FA\Utils
*
* @since 2.8.0
*/
class White_Label {
/**
* Local static cache for plugins settings.
*
* @var array
*
* @since 2.8.0
*/
private static $plugin_settings = array();
/**
* Inits the plugin related classes and settings
*
* @return void
*
* @since 2.8.0
*/
public static function init() {
self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() );
}
/**
* Util function to grab white label settings or apply defaults if no settings are saved into the db.
*
* @param string $setting_name Settings to grab value of.
* @param boolean $get_default_on_empty return default setting value if current one is empty.
*
* @return string|array Settings value or default value.
*
* @since 2.8.0
*/
public static function get_setting( $setting_name = '', $get_default_on_empty = false ) {
$default_settings = self::get_default_settings();
$white_label_setting = self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ];
// If we have no setting name, return them all.
if ( empty( $setting_name ) ) {
return $white_label_setting;
}
// First lets check if any options have been saved.
if ( empty( $white_label_setting ) || ! isset( $white_label_setting ) ) {
$apply_defaults = true;
}
if ( $apply_defaults ) {
return isset( $default_settings[ $setting_name ] ) ? $default_settings[ $setting_name ] : '';
} elseif ( ! isset( $white_label_setting[ $setting_name ] ) ) {
if ( true === $get_default_on_empty ) {
if ( isset( $default_settings[ $setting_name ] ) ) {
return $default_settings[ $setting_name ];
}
}
return '';
} else {
return $white_label_setting[ $setting_name ];
}
}
/**
* Array with all the plugin default settings.
*
* @return array
*
* @since 2.8.0
*/
public static function get_default_settings() {
$default_settings = array(
'default-text-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
'default-text-pw-reset-code-page' => '<p>' . __( 'You have been sent a one-time code via email. Please enter the code below and then click Get New Password to proceed with the password reset.', 'wp-2fa' ) . '</p><br><p><strong>' . __( 'Note: If you have not received the code please click the button Resend Code. If you still do not get the code after pressing the button, please contact the website\'s administrator.', 'wp-2fa' ) . '</strong></p>',
'default-2fa-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
'default-2fa-resetup-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
'custom-text-authy-code-page-intro' => __( 'If you are using the Authy app approve the OneTouch request to log in.', 'wp-2fa' ),
'custom-text-authy-code-page-awaiting' => __( 'Waiting for approval from application...', 'wp-2fa' ),
'custom-text-authy-code-page' => __( 'Manually enter the code from the mobile app.', 'wp-2fa' ),
'custom-text-twilio-code-page' => __( 'Enter the 2FA code you have received over SMS.', 'wp-2fa' ),
'custom-text-clickatell-code-page' => __( 'Enter the 2FA code you have received over SMS.', 'wp-2fa' ),
'custom-text-yubico-code-page' => __( 'Please insert the YubiKey in a USB port and touch / click the button on the YubiKey to generate the OTP required to log in.', 'wp-2fa' ),
'custom-text-app-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
'custom-text-email-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
'default-backup-code-page' => __( 'Enter a backup verification code.', 'wp-2fa' ),
'method_invalid_setting' => 'login_block',
'enable_wizard_styling' => 'enable_wizard_styling',
'show_help_text' => 'show_help_text',
'enable_wizard_logo' => '',
'enable_welcome' => '',
'welcome' => '',
'method_selection' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3>' . esc_html__(
'There are {available_methods_count} methods available to choose from for 2FA:',
'wp-2fa'
),
'method_selection_single' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3><p>' . __( 'Only the below 2FA method is allowed on this website:', 'wp-2fa' ) . '</p>',
'method_help_authy_intro' => '<h3>' . __( 'Setting up Push notifications', 'wp-2fa' ) . '</h3><p>' . __( 'To enable push notifications enter the country and cellphone number in order to use it with this account.', 'wp-2fa' ) . '</p>',
'method_help_twilio_intro' => '<h3>' . __( 'Setting up 2FA over SMS', 'wp-2fa' ) . '</h3><p>' . __( 'When you use 2FA over SMS to log in to this website you will receive your one-time code via an SMS on your cellphone. Therefore please enter the cellphone number of where you would like to receive the SMS below.', 'wp-2fa' ) . '</p>',
'method_help_clickatell_intro' => '<h3>' . __( 'Setting up 2FA over SMS', 'wp-2fa' ) . '</h3><p>' . __( 'When you use 2FA over SMS to log in to this website you will receive your one-time code via an SMS on your cellphone. Therefore please enter the cellphone number of where you would like to receive the SMS below.', 'wp-2fa' ) . '</p>',
'method_help_oob_intro' => '<h3>' . __( 'Setting up Link over email 2FA', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the email address to where the out-of-band link should be sent:', 'wp-2fa' ) . '</p>',
'method_help_yubico_intro' => '<h3>' . __( 'Setting up 2FA with YubiKey', 'wp-2fa' ) . '</h3><p>' . __( '1 - Insert your YubiKey into the computer\'s / mobile\'s USB port', 'wp-2fa' ) . '</p><p>' . __( '2 - Touch / press the button on your YubiKey to generate the OTP code, which is automatically populated below', 'wp-2fa' ) . '</p>',
'method_verification_oob_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent to your email address to finalize the setup. Once the code is confirmed and 2FA is set up, you only have to verify a login by clicking on a link sent to you via email.', 'wp-2fa' ) . '</p>',
'method_verification_authy_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the code from your Authy application with name {authy_name}', 'wp-2fa' ) . '</p>',
'method_verification_twilio_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent via SMS to your phone to confirm your phone number.', 'wp-2fa' ) . '</p>',
'method_verification_clickatell_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent via SMS to your phone to confirm your phone number.', 'wp-2fa' ) . '</p>',
'method_verification_yubico_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Touch the YubiKey again to generate the OTP code to confirm the setup. Once the code is populated below, it should be automatically saved and verified. If that does not happen by any reason, once the secret key was pasted, click "Validate & save" button below to manually save and complete the configuration.', 'wp-2fa' ) . '</p>',
'backup_codes_intro_multi' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'It is recommended to configure a backup 2FA method in case you do not have access to the primary 2FA method to generate a code to log in. You can configure any of the below. You can always configure any or both from your user profile page later.', 'wp-2fa' ) . '</p>',
'backup_codes_intro' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Congratulations! You have enabled two-factor authentication for your user. You’ve just helped towards making this website more secure!', 'wp-2fa' ) . '</p>',
'backup_codes_intro_continue' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Congratulations! You have enabled two-factor authentication for your user. You’ve just helped towards making this website more secure!', 'wp-2fa' ) . '</p><p>' . __( 'You should now generate the list of backup method. Although this is optional, it is highly recommended to have a secondary 2FA method. This can be used as a backup should the primary 2FA method fail. This can happen if, for example, you forget your smartphone, the smartphone runs out of battery, or there are email deliverability problems.', 'wp-2fa' ) . '</p>',
'backup_codes_generate_intro' => '<h3>' . __( 'Generate list of backup codes', 'wp-2fa' ) . '</h3><p>' . __( 'It is recommended to generate and print some backup codes in case you lose access to your primary 2FA method.', 'wp-2fa' ) . '</p>',
'backup_codes_generated' => '<h3>' . __( 'Backup codes generated', 'wp-2fa' ) . '</h3><p>' . __( 'Here are your backup codes:', 'wp-2fa' ) . '</p>',
'no_further_action' => '<h3>' . __( 'Congratulations! You are all set.', 'wp-2fa' ),
'2fa_required_intro' => '<h3>' . __( 'You are required to configure 2FA.', 'wp-2fa' ) . '</h3><p>' . __( 'In order to keep this site - and your details secure, this website’s administrator requires you to enable 2FA authentication to continue.', 'wp-2fa' ) . '</p><p>' . __( 'Two factor authentication ensures only you have access to your account by creating an added layer of security when logging in -', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank" rel="noopener">' . __( 'Learn more', 'wp-2fa' ) . '</a></p>',
'authy_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification method', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the push notifications configuration.', 'wp-2fa' ) . '</p>',
'authy_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'twilio_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method (Twilio)', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the mobile phone number where the one-time code should be sent.', 'wp-2fa' ) . '</p>',
'clickatell_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method (Clickatell)', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where code should be send:', 'wp-2fa' ) . '</p>',
'yubico_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} 2FA over YubiKey', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the YubiKey associated with your user.', 'wp-2fa' ) . '</p>',
'twilio_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA over SMS service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'clickatell_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA over SMS service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'yubico_reconfigure_intro_unavailable' => '<h3>' . __( ' {reconfigure_or_configure_capitalized} 2FA over YubiKey', 'wp-2fa' ) . '</h3><p>' . __( 'The Yubico service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'oob_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} link over email method', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the email address where the link should be sent.', 'wp-2fa' ) . '</p>',
'custom_css' => '',
'login_custom_css' => '',
'logo-code-page' => '',
'disable_login_css' => '',
'login-to-view-area' => '<p>' . __( 'You must be logged in to view this page. {login_url}', 'wp-2fa' ) . '</p>',
'backup_email_intro' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Well done on configuring 2FA, your login has just got more secure. To make sure you never get locked out you are required to confirm your email address and use email as an alternative and backup 2FA method in case your primary method is unavailable. Please confirm your email address below', 'wp-2fa' ) . '</p>',
'user-profile-form-preamble-title' => __( 'Two-factor authentication settings', 'wp-2fa' ),
'user-profile-form-preamble-desc' => __( 'Add two-factor authentication to strengthen the security of your user account.', 'wp-2fa' ),
'use_custom_2fa_message' => 'use-defaults',
);
/**
* Gives the ability to filter the default settings array of the plugin
*
* @param array $settings - The array with all the default settings.
*
* @since 2.0.0
*/
$default_settings = \apply_filters( WP_2FA_PREFIX . 'white_label_default_settings', $default_settings );
return $default_settings;
}
}
}
includes/classes/Utils/class-abstract-migration.php 0000644 00000015620 15075513060 0016523 0 ustar 00 <?php
/**
* Abstract migration class.
*
* @package wp2fa
* @subpackage utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Utils;
use WP2FA\Utils\Settings_Utils;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* Abstract AMigration class
*/
if ( ! class_exists( '\WP2FA\Utils\Abstract_Migration' ) ) {
/**
* Utility class to ease the migration process.
*
* Every migration must go in its own method
* The naming convention is migrateUpTo_XXX where XXX is the number of the version,
* format is numbers only.
* Example: migration for version upto 1.4 must be in migrateUpTo_14 method
*
* The numbers in the names of the methods must have exact numbers count as in the selected
* version in use, even if there are silent numbers for some of the major versions as 1, 2, 3 etc. (the .0.0 is skipped / silent)
* Example:
* - if X.X.X is selected for version number, then for version 1.1 method must have "...migrateUpTo_110..." in its name
* - if X.X is selected for version number, then for version 1, method must have "...migrateUpTo_10..." in its name
*
* Note: you can add prefix to the migration method, if that is necessary, but "migrateUpTo_" is a must -
* the name must contain that @see getAllMigrationMethodsAsNumbers of that class.
* For version extraction the number following the last '_' will be used
* TODO: the mandatory part of the method name can be a setting in the class, but is that a good idea?
*
* Note: order of the methods is not preserved - version numbers will be used for ordering
*
* @package WP2FA\Utils
*
* @since 1.6
*/
class Abstract_Migration {
/**
* Extracted version from the DB (WP option)
*
* @var string
*
* @since 1.6.0
*/
protected static $stored_version = '';
/**
* The name of the option from which we should extract version
* Note: version is expected in version format - 1.0.0; 1; 1.0; 1.0.0.0
* Note: only numbers will be processed
*
* @var string
*
* @since 1.6.0
*/
protected static $version_option_name = '';
/**
* The constant name where the plugin version is stored
* Note: version is expected in version format - 1.0.0; 1; 1.0; 1.0.0.0
* Note: only numbers will be processed
*
* @var string
*
* @since 2.2.0
*/
protected static $const_name_of_plugin_version = '';
/**
* Used for adding proper pads for the missing numbers
* Version number format used here depends on selection for how many numbers will be used for representing version
*
* For X.X use 2;
* For X.X.X use 3;
* For X.X.X.X use 4;
*
* etc.
*
* Example: if selected version format is X.X.X that means that 3 digits are used for versioning.
* And current version is stored as 2 (no suffix 0.0) that means that it will be normalized as 200.
*
* @var integer
*
* @since 1.6.0
*/
protected static $pad_length = 3;
/**
* Collects all the migration methods which needs to be executed in order and executes them
*
* @return void
*
* @since 1.6.0
*/
public static function migrate() {
if ( version_compare( static::get_stored_version(), \constant( static::$const_name_of_plugin_version ), '<' ) ) {
$stored_version_as_number = static::normalize_version( static::get_stored_version() );
$target_version_as_number = static::normalize_version( \constant( static::$const_name_of_plugin_version ) );
$method_as_version_numbers = static::get_all_migration_methods_as_numbers();
$migrate_methods = array_filter(
$method_as_version_numbers,
function ( $method, $key ) use ( &$stored_version_as_number, &$target_version_as_number ) {
if ( $target_version_as_number > $stored_version_as_number ) {
return ( in_array( $key, range( $stored_version_as_number, $target_version_as_number ), true ) );
}
return false;
},
ARRAY_FILTER_USE_BOTH
);
if ( ! empty( $migrate_methods ) ) {
\ksort( $migrate_methods );
foreach ( $migrate_methods as $method ) {
static::{$method}();
}
}
// If we have a previous version, its an update so flag notice.
if ( ! empty( Settings_Utils::get_option( static::$version_option_name ) ) ) {
Settings_Utils::update_option( 'wp_2fa_update_redirection_needed', true );
}
self::store_updated_version();
}
/**
* Downgrading the plugin? Set the version number.
* Leave the rest as is.
*
* @return void
*
* @since 2.2.0
*/
if ( version_compare( static::get_stored_version(), \constant( static::$const_name_of_plugin_version ), '>' ) ) {
self::store_updated_version();
}
}
/**
* Extracts currently stored version from the DB
*
* @return string
*
* @since 1.6.0
*/
private static function get_stored_version() {
if ( '' === trim( (string) static::$stored_version ) ) {
static::$stored_version = (string) Settings_Utils::get_option( static::$version_option_name, '0.0.0' );
}
return static::$stored_version;
}
/**
* Stores the version to which we migrated
*
* @return void
*
* @since 1.6.0
*/
private static function store_updated_version() {
Settings_Utils::update_option( static::$version_option_name, \constant( static::$const_name_of_plugin_version ) );
}
/**
* Normalized the version numbers to numbers
*
* Version format is expected to be as follows:
* X.X.X
*
* All non numeric values will be removed from the version string
*
* Note: version is expected in version format - 1.0.0; 1; 1.0; 1.0.0.0
* Note: only numbers will be processed
*
* @param string $version - The version string we have to use.
*
* @return string
*
* @since 1.6.0
*/
private static function normalize_version( string $version ) {
$version_as_number = (int) filter_var( $version, FILTER_SANITIZE_NUMBER_INT );
if ( self::$pad_length > strlen( (string) $version_as_number ) ) {
$version_as_number = str_pad( (string) $version_as_number, static::$pad_length, '0', STR_PAD_RIGHT );
}
return $version_as_number;
}
/**
* Collects all the migration methods from the class and stores them in the array
* Array is in following format:
* key - number of the version
* value - name of the method
*
* @return array
*
* @since 1.6.0
*/
private static function get_all_migration_methods_as_numbers() {
$class_methods = \get_class_methods( get_called_class() );
$method_as_version_numbers = array();
foreach ( $class_methods as $method ) {
if ( false !== \strpos( $method, 'migrate_up_to_' ) ) {
$ver = \substr( $method, \strrpos( $method, '_' ) + 1, \strlen( $method ) );
$method_as_version_numbers[ $ver ] = $method;
}
}
return $method_as_version_numbers;
}
}
}
includes/classes/Utils/class-request-utils.php 0000644 00000002777 15075513060 0015570 0 ustar 00 <?php
/**
* Responsible for the requests.
*
* @package wp2fa
* @subpackage utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
* @since 2.0.0
*/
declare(strict_types=1);
namespace WP2FA\Utils;
if ( ! class_exists( '\WP2FA\Utils\Request_Utils' ) ) {
/**
* Utility class to extract info from current request.
*
* @package WP2FA\Utils
* @since 2.0.0
*/
class Request_Utils {
/**
* Extracts the IP address for the currently browsing user
*
* @return string
*
* @since 2.0.0
*/
public static function get_ip() {
foreach (
array(
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR',
) as $key
) {
if ( array_key_exists( $key, $_SERVER ) === true ) {
foreach ( array_map( 'trim', explode( ',', $_SERVER[ $key ] ) ) as $ip ) { // phpcs:ignore
if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
return $ip;
}
}
}
}
}
/**
* Extracts the User agent for the currently request.
*
* @return string
*
* @since 2.0.0
*/
public static function get_user_agent() {
if ( ! array_key_exists( 'HTTP_USER_AGENT', $_SERVER ) ) {
return '';
}
return trim( (string) $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore
}
}
}
includes/classes/App/index.php 0000644 00000000046 15075513060 0012351 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/App/grace-period/class-grace-period.php 0000644 00000014573 15075513060 0017261 0 ustar 00 <?php
/**
* Main file of the grace period settings extension class.
*
* @package wp2fa
* @subpackage grace-period
* @since 2.0.0
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\App;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* Grace period class
*/
if ( ! class_exists( '\WP2FA\App\Grace_Period' ) ) {
/**
* Responsible for users which grace period has expired
*
* Gives the administrator the ability to select what action the plugin should take:
* - Lock the user
* - Force the user to set up their 2FA immediately
*
* @since 2.0.0
*/
class Grace_Period {
/**
* Inits all the hooks
*
* @return void
*
* @since 2.0.0
*/
public static function init() {
\add_filter( WP_2FA_PREFIX . 'after_grace_period', array( __CLASS__, 'grace_period_options' ), 10, 5 );
\add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'add_setting_value' ) );
\add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
\add_filter( WP_2FA_PREFIX . 'should_account_be_locked_on_grace_period_expiration', array( __CLASS__, 'maybe_prevent_account_lock' ), 10, 2 );
}
/**
* Prevent account locking if allowed, depending on the plugin settings.
*
* @param boolean $state - Current state of the checking.
* @param \WP_User $user - The User class.
*
* @return bool
*
* @since 2.0.0
*/
public static function maybe_prevent_account_lock( bool $state, \WP_User $user ) {
if ( 'configure-right-away' === Settings::get_role_or_default_setting( 'grace-policy-after-expire-action', $user ) ) {
User_Helper::set_user_enforced_instantly( true, $user );
return false;
}
return $state;
}
/**
* Collects the options for the main plugin settings page and returns them
*
* @param string $content - HTML content.
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @return string
*
* @since 2.0.0
*/
public static function grace_period_options( string $content, string $role = '', string $name_prefix = '', string $data_role = '', string $role_id = '' ) {
return $content . self::grace_options( $role, $name_prefix, $data_role, $role_id );
}
/**
* Adds global plugin setting options
*
* @param array $loop_settings - Array with current plugin settings.
*
* @return array
*
* @since 2.0.0
*/
public static function add_setting_value( array $loop_settings ) {
$loop_settings[] = 'grace-policy-after-expire-action';
return $loop_settings;
}
/**
* Checks the grace policy setting for the given user
*
* @param \WP_User $user - The user for which we have to check the settings.
*
* @return boolean
*
* @since 2.0.0
*/
public static function is_set_up_immediately_set( \WP_User $user ) {
if ( 'configure-right-away' === Settings::get_role_or_default_setting( 'grace-policy-after-expire-action', $user ) ) {
return true;
}
return false;
}
/**
* Adds the extension default settings to the main plugin settings
*
* @param array $default_settings - array with plugin default settings.
*
* @return array
*
* @since 2.0.0
*/
public static function add_default_settings( array $default_settings ) {
$default_settings['grace-policy-after-expire-action'] = 'configure-right-away';
return $default_settings;
}
/**
* Adds options to the settings page
*
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @return string
*
* @since 2.0.0
*/
private static function grace_options( string $role = '', string $name_prefix = '', string $data_role = '', string $role_id = '' ): string {
ob_start();
if ( class_exists( 'WP2FA\Extensions\RoleSettings\Role_Settings_Controller' ) ) {
$expire_action = Role_Settings_Controller::get_setting( $role, 'grace-policy-after-expire-action', 'configure-right-away' );
} else {
$expire_action = Settings::get_role_or_default_setting( 'grace-policy-after-expire-action', null, null, 'configure-right-away' );
}
if ( false === $expire_action ) {
$expire_action = 'configure-right-away';
}
?>
<div class="sub-setting-indent">
<p class="description" style="margin-top: 15px; margin-bottom: 8px;">
<?php echo \esc_html__( 'What should the plugin do with users who do not configure 2FA within the grace period?', 'wp-2fa' ); ?>
</p>
<fieldset>
<label for="configure-right-away<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 10px; display: inline-block;">
<input type="radio" name="<?php echo \esc_attr( $name_prefix ); ?>[grace-policy-after-expire-action]"
id="configure-right-away<?php echo \esc_attr( $role_id ); ?>"
<?php echo $data_role; // phpcs:ignore?>
value="configure-right-away" <?php checked( $expire_action, 'configure-right-away' ); ?> class="js-nested">
<span><?php echo \esc_html__( 'Do not let them access the dashboard / user page once they log in until they configure 2FA', 'wp-2fa' ); ?></span>
</label>
<br>
<div style="clear:both">
<label for="manual-block<?php echo \esc_attr( $role_id ); ?>">
<input type="radio" name="<?php echo \esc_attr( $name_prefix ); ?>[grace-policy-after-expire-action]" <?php checked( $expire_action, 'manual-block' ); ?>
id="manual-block<?php echo \esc_attr( $role_id ); ?>"
<?php echo $data_role; // phpcs:ignore?>
value="manual-block" class="js-nested">
<span><?php echo \esc_html__( 'Block the user (administrators have to manually unblock them)', 'wp-2fa' ); ?></span>
</label>
</div>
</fieldset>
</div>
<?php
$html_content = ob_get_contents();
ob_end_clean();
return $html_content;
}
}
}
includes/classes/App/grace-period/index.php 0000644 00000000046 15075513060 0014712 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Shortcodes/class-shortcodes.php 0000644 00000020335 15075513060 0016122 0 ustar 00 <?php
/**
* Responsible for rendering the short codes.
*
* @package wp2fa
* @subpackage short-codes
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Shortcodes;
use WP2FA\Core;
use WP2FA\WP2FA;
use WP2FA\Admin\User_Notices;
use WP2FA\Admin\User_Profile;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Views\Re_Login_2FA;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
if ( ! class_exists( '\WP2FA\Shortcodes\Shortcodes' ) ) {
/**
* Class for rendering shortcodes.
*/
class Shortcodes {
/**
* Constructor.
*/
public static function init() {
\add_shortcode( 'wp-2fa-setup-form', array( __CLASS__, 'user_setup_2fa_form' ) );
\add_shortcode( 'wp-2fa-setup-notice', array( __CLASS__, 'user_setup_2fa_notice' ) );
\add_action( 'wp_enqueue_scripts', array( __CLASS__, 'register_2fa_shortcode_scripts' ) );
}
/**
* Register scripts and styles.
*/
public static function register_2fa_shortcode_scripts() {
// Add our front end stuff, which we only want to load when the shortcode is present.
\wp_register_script( 'wp_2fa_frontend_scripts', Core\script_url( 'wp-2fa', 'admin' ), array( 'jquery', 'wp_2fa_micro_modals' ), WP_2FA_VERSION, true );
\wp_register_script( 'wp_2fa_micro_modals', Core\script_url( 'micromodal', 'admin' ), array(), WP_2FA_VERSION, true );
\wp_register_style( 'wp_2fa_styles', Core\style_url( 'styles', 'frontend' ), array(), WP_2FA_VERSION );
$data_array = array(
'ajaxURL' => \admin_url( 'admin-ajax.php' ),
'roles' => WP_Helper::get_roles_wp(),
'nonce' => \wp_create_nonce( 'wp-2fa-settings-nonce' ),
'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ),
'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ),
'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ),
'allDoneHeading' => \esc_html__( 'All done.', 'wp-2fa' ),
'allDoneText' => \esc_html__( 'Your login just got more secure.', 'wp-2fa' ),
'closeWizard' => \esc_html__( 'Close Wizard', 'wp-2fa' ),
'invalidEmail' => \esc_html__( 'Please use a valid email address', 'wp-2fa' ),
);
\wp_localize_script( 'wp_2fa_frontend_scripts', 'wp2faData', $data_array );
$role = User_Helper::get_user_role();
$re_login = Settings::get_role_or_default_setting( Re_Login_2FA::RE_LOGIN_SETTINGS_NAME, 'current', $role );
$data_array = array(
'ajaxURL' => \admin_url( 'admin-ajax.php' ),
'nonce' => \wp_create_nonce( 'wp2fa-verify-wizard-page' ),
'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ),
'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ),
'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ),
'invalidEmail' => \esc_html__( 'Please use a valid email address', 'wp-2fa' ),
'backupCodesSent' => \esc_html__( 'Backup codes sent', 'wp-2fa' ),
'reLogin' => $re_login,
'reLoginEnabled' => Re_Login_2FA::ENABLED_SETTING_VALUE,
);
$redirect_page = Settings::get_role_or_default_setting( 'redirect-user-custom-page-global', 'current', $role );
$data_array['redirectToUrl'] = ( '' !== trim( (string) $redirect_page ) ) ? \trailingslashit( get_site_url() ) . $redirect_page : '';
// Check and override if custom redirect page is selected and custom redirect is set.
if (
'yes' === Settings::get_role_or_default_setting( 'create-custom-user-page', 'current', $role ) ||
'yes' === Settings::get_role_or_default_setting( 'create-custom-user-page' ) ) {
if (
'' !== trim( (string) Settings::get_role_or_default_setting( 'redirect-user-custom-page', 'current', $role ) ) ||
'' !== trim( (string) Settings::get_role_or_default_setting( 'redirect-user-custom-page' ) ) ) {
if ( 'yes' === Settings::get_role_or_default_setting( 'create-custom-user-page', 'current', $role ) ) {
$data_array['redirectToUrl'] = \trailingslashit( get_site_url() ) . Settings::get_role_or_default_setting( 'redirect-user-custom-page', 'current', $role );
} else {
$data_array['redirectToUrl'] = \trailingslashit( get_site_url() ) . Settings::get_role_or_default_setting( 'redirect-user-custom-page' );
}
}
}
// Check for shortcode parameter - if one is present use it to redirect the user - highest priority.
if ( isset( $redirect_after ) && ! empty( $redirect_after ) ) {
$data_array['redirectToUrl'] = \trailingslashit( \get_site_url() ) . \urlencode( $redirect_after );
} elseif ( isset( $_GET['return'] ) && ! empty( $_GET['return'] ) ) {
$data_array['redirectToUrl'] = \trailingslashit( \get_site_url() ) . strip_tags( \wp_unslash( $_GET['return'] ) ); // phpcs:ignore
}
\wp_localize_script( 'wp_2fa_frontend_scripts', 'wp2faWizardData', $data_array );
}
/**
* Output setup form.
*
* @param array $atts - Array with the attributes passed to shortcode.
*
* @return string
*/
public static function user_setup_2fa_form( $atts ) {
/** Shortcode redirect_after is supported, with which the user can override all other settings */
extract( // phpcs:ignore
\shortcode_atts(
array(
'show_preamble' => 'true',
'redirect_after' => '',
'do_not_show_enabled' => 'false',
),
$atts
)
);
/**
* Fires when the FE shortcode scripts are registered.
*
* @param bool $shortcodes - True if called from the short codes method.
*
* @since 2.2.0
*/
\do_action( WP_2FA_PREFIX . 'shortcode_scripts', true );
if ( is_user_logged_in() ) {
\wp_enqueue_script( 'wp_2fa_frontend_scripts' );
\wp_enqueue_style( 'wp_2fa_styles' );
ob_start();
echo '<form id="your-profile" class="wp-2fa-configuration-form">';
User_Profile::inline_2fa_profile_form( 'output_shortcode', $show_preamble, array( 'do_not_show_enabled' => $do_not_show_enabled ) );
echo '</form>';
$content = ob_get_contents();
ob_end_clean();
return $content;
} elseif ( ! is_admin() && ! is_user_logged_in() ) {
ob_start();
$new_page_id = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
$redirect_to = ! empty( $new_page_id ) ? \get_permalink( $new_page_id ) : \get_home_url();
$link_markup = '<a href="' . \esc_url( \wp_login_url( $redirect_to ) ) . '">' . \esc_html__( 'Login here.', 'wp-2fa' ) . '</a>';
$message = '<p id="wp_2fa_login_to_view_text">' . str_replace( '{login_url}', $link_markup, WP2FA::get_wp2fa_white_label_setting( 'login-to-view-area', true ) ) . '</p>';
echo \wp_kses_post( $message );
$content = ob_get_contents();
ob_end_clean();
return $content;
}
}
/**
* Output setup nag.
*
* @param array $atts - Array with the attributes passed to shortcode.
*
* @return string
*/
public static function user_setup_2fa_notice( $atts ) {
extract( // phpcs:ignore
\shortcode_atts(
array(
'configure_2fa_url' => '',
),
$atts
)
);
// TODO: is that really necessary?
User_Notices::init();
if ( ! is_admin() && is_user_logged_in() ) {
\wp_enqueue_script( 'wp_2fa_micro_modals' );
\wp_enqueue_script( 'wp_2fa_frontend_scripts' );
\wp_enqueue_style( 'wp_2fa_styles' );
$data_array = array(
'ajaxURL' => \admin_url( 'admin-ajax.php' ),
'roles' => WP_Helper::get_roles_wp(),
'nonce' => \wp_create_nonce( 'wp-2fa-settings-nonce' ),
'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ),
'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ),
'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ),
'allDoneHeading' => \esc_html__( 'All done.', 'wp-2fa' ),
'allDoneText' => \esc_html__( 'Your login just got more secure.', 'wp-2fa' ),
'closeWizard' => \esc_html__( 'Close Wizard', 'wp-2fa' ),
);
\wp_localize_script( 'wp_2fa_frontend_scripts', 'wp2faData', $data_array );
ob_start();
User_Notices::user_setup_2fa_nag( 'output_shortcode', $configure_2fa_url );
$content = ob_get_contents();
ob_end_clean();
return $content;
}
return '';
}
}
}
includes/classes/Shortcodes/index.php 0000644 00000000046 15075513060 0013746 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/Fly-Out/assets/css/flyout.css 0000644 00000010253 15075513060 0016467 0 ustar 00 #mlp-flyout {
position: fixed;
z-index: 100049;
transition: all 0.3s ease-in-out;
right: 40px;
bottom: 40px;
opacity: 1;
}
#mlp-overlay {
background: #000;
opacity: 0.4;
filter: alpha(opacity=40);
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
z-index: 100049;
}
#mlp-flyout a:focus {
outline: none;
box-shadow: none;
}
#mlp-flyout #mlp-elmnts-button {
display: block;
}
#mlp-flyout #mlp-elmnts-image-wrapper {
border: 3px solid #000000;
border-radius: 50%;
padding: 0;
display: block;
overflow: hidden;
background: #000000;
box-shadow: 0 3px 20px rgba(0, 0, 0, 0.2);
}
#mlp-flyout #mlp-elmnts-button img {
width: 55px;
height: 55px;
display: block;
overflow: hidden;
padding: 2px;
box-sizing: border-box;
position: relative;
top: 4px;
}
#mlp-flyout #mlp-elmnts-button:hover #mlp-elmnts-image-wrapper {
box-shadow: 0 3px 30px rgba(0, 0, 0, 0.25);
}
#mlp-flyout:not(.opened) #mlp-elmnts-button:hover .mlp-elmnts-label {
opacity: 1;
margin-right: 0;
}
#mlp-flyout .mlp-elmnts-label {
position: absolute;
display: block;
top: 50%;
right: calc(100% + 25px);
transform: translateY(-50%) scale(1);
-moz-transform: translateY(-50%);
-webkit-transform: translateY(-50%);
color: #fff;
background: #444 0 0 no-repeat padding-box;
font-size: 14px;
white-space: nowrap;
padding: 5px 10px;
height: auto !important;
line-height: initial;
transition: all 0.2s ease-out;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
opacity: 0;
margin-right: -50px;
}
#mlp-flyout .mlp-elmnts-icon {
width: 40px;
height: 40px;
vertical-align: middle;
line-height: 60px;
text-align: center;
}
#mlp-flyout .mlp-elmnts-icon img {
max-width: 70%;
filter: brightness(100);
}
#mlp-flyout .mlp-elmnts-label.visible {
opacity: 1;
}
#mlp-flyout .mlp-elmnts-menu-item {
position: absolute;
left: 10px;
width: 40px;
height: 40px;
opacity: 0;
visibility: hidden;
transform: scale(0);
border-radius: 50%;
box-shadow: 0 3px 20px rgba(0, 0, 0, 0.2);
background: #1c3aa9;
text-align: center;
vertical-align: middle;
text-decoration: none;
transition-timing-function: ease-in-out;
}
#mlp-flyout .mlp-elmnts-menu-item.accent {
background: #ca4a1f;
}
#mlp-flyout.opened .mlp-elmnts-menu-item {
opacity: 1;
visibility: visible;
transform: scale(1);
}
#mlp-flyout .mlp-elmnts-menu-item:hover {
box-shadow: 0 3px 30px rgba(0, 0, 0, 0.25);
}
#mlp-flyout .mlp-elmnts-menu-item:hover .mlp-elmnts-label {
right: calc(100% + 55px);
}
#mlp-flyout .mlp-elmnts-menu-item .mlp-elmnts-label {
right: calc(100% + 70px);
}
#mlp-flyout .mlp-elmnts-menu-item .dashicons {
line-height: 41px;
font-size: 23px;
color: #fff;
padding: 0px 3px 0px 0;
}
.mlp-elmnts-menu-item-1 {
bottom: 75px;
transition: transform 0.2s 30ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-2 {
bottom: 130px;
transition: transform 0.2s 70ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-3 {
bottom: 185px;
transition: transform 0.2s 110ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-4 {
bottom: 240px;
transition: transform 0.2s 150ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-5 {
bottom: 295px;
transition: transform 0.2s 190ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-6 {
bottom: 350px;
transition: transform 0.2s 230ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-7 {
bottom: 405px;
transition: transform 0.2s 270ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-8 {
bottom: 460px;
transition: transform 0.2s 310ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-9 {
bottom: 515px;
transition: transform 0.2s 350ms, background-color 0.2s;
}
.mlp-elmnts-menu-item-10 {
bottom: 570px;
transition: transform 0.2s 390ms, background-color 0.2s;
}
includes/classes/Admin/Fly-Out/assets/js/flyout.js 0000644 00000000742 15075513060 0016141 0 ustar 00
jQuery(document).ready(function () {
jQuery('#mlp-elmnts-button').on('click', function (e) {
e.preventDefault();
jQuery('#mlp-flyout').toggleClass('opened');
jQuery('#mlp-overlay').toggle();
return false;
}); // open/close menu
jQuery('#mlp-overlay').on('click', function (e) {
e.preventDefault();
jQuery(this).hide();
jQuery('#mlp-flyout').removeClass('opened');
return false;
}); // click on overlay - hide menu
}); // jQuery ready
includes/classes/Admin/Fly-Out/class-flyout.php 0000644 00000016653 15075513060 0015511 0 ustar 00 <?php
/**
* Responsible for fly-out menu shown on some of the plugin pages.
*
* @package wp2fa
* @subpackage flyout
*
* @since 2.8.0
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin\FlyOut;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( '\WP2FA\Admin\FlyOut\FlyOut' ) ) {
/**
* Generates fly-out menu on the plugin admin screen.
*
* @since 2.8.0
*/
class FlyOut {
private const ENQUEUE_NAME = 'mlp_flyout';
private const CONFIG_TRANSIENT_NAME = \WP_2FA_PREFIX . 'flyout_config';
/**
* Array with the configuration of the fly-out menu
*
* @var array
*
* @since 2.8.0
*/
private static $config = array();
/**
* Class cache for the current screen (if admin is on it)
*
* @var bool
*
* @since 2.8.0
*/
private static $screen = null;
/**
* Inits the class and its hooks
*
* @return void
*
* @since 2.8.0
*/
public static function init() {
if ( ! \is_admin() ) {
return;
} else {
self::load_config();
if ( ! empty( self::$config ) ) {
\add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
\add_action( 'admin_head', array( __CLASS__, 'admin_head' ) );
\add_action( 'admin_footer', array( __CLASS__, 'admin_footer' ) );
}
}
}
/**
* Loads the external config for processing
*
* @return void
*
* @since 2.8.0
*/
public static function load_config() {
$config = array();
$config = self::read_remote_config();
if ( false === $config ) {
return;
}
$defaults = array(
'plugin_screen' => '',
'icon_border' => '#0000ff',
'icon_right' => '40px',
'icon_bottom' => '40px',
'icon_image' => '',
'icon_padding' => '2px',
'icon_size' => '55px',
'menu_accent_color' => '#ca4a1f',
'custom_css' => '',
'menu_items' => array(),
);
$config = array_merge( $defaults, (array) $config );
if ( ! is_array( $config['plugin_screen'] ) ) {
$config['plugin_screen'] = array( $config['plugin_screen'] );
}
self::$config = $config;
}
/**
* Checks the current screen and returns true if it is the plugin one
*
* @return boolean
*
* @since 2.8.0
*/
public static function is_plugin_screen(): bool {
if ( \is_null( self::$screen ) ) {
$screen = \get_current_screen();
self::$screen = false;
if ( in_array( $screen->id, self::$config['plugin_screen'] ) ) {
self::$screen = true;
}
}
return self::$screen;
}
/**
* Loads the fly-out css and JS files
*
* @return void
*
* @since 2.8.0
*/
public static function admin_enqueue_scripts() {
if ( false === self::is_plugin_screen() ) {
return;
}
\wp_enqueue_style(
self::ENQUEUE_NAME,
WP_2FA_URL . '/includes/classes/Admin/Fly-Out/assets/css/flyout.css',
array(),
WP_2FA_VERSION
);
\wp_enqueue_script(
self::ENQUEUE_NAME,
WP_2FA_URL . '/includes/classes/Admin/Fly-Out/assets/js/flyout.js',
array(),
WP_2FA_VERSION,
true
);
}
/**
* Writes additional custom code in the header of the page
*
* @return void
*
* @since 2.8.0
*/
public static function admin_head() {
if ( false === self::is_plugin_screen() ) {
return;
}
$out = '<style type="text/css">';
$out .= '#mlp-flyout {
right: ' . \sanitize_text_field( self::$config['icon_right'] ) . ';
bottom: ' . \sanitize_text_field( self::$config['icon_bottom'] ) . ';
}';
$out .= '#mlp-flyout #mlp-elmnts-image-wrapper {
border: ' . \sanitize_text_field( self::$config['icon_border'] ) . ';
}';
$out .= '#mlp-flyout #mlp-elmnts-button img {
padding: ' . \sanitize_text_field( self::$config['icon_padding'] ) . ';
width: ' . \sanitize_text_field( self::$config['icon_size'] ) . ';
height: ' . \sanitize_text_field( self::$config['icon_size'] ) . ';
}';
$out .= '#mlp-flyout .mlp-elmnts-menu-item.accent {
background: ' . \sanitize_text_field( self::$config['menu_accent_color'] ) . ';
}';
$out .= \sanitize_text_field( self::$config['custom_css'] );
$out .= '</style>';
echo $out; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Writes additional custom code in the footer of the page
*
* @return void
*
* @since 2.8.0
*/
public static function admin_footer() {
if ( false === self::is_plugin_screen() ) {
return;
}
$out = '';
$icons_url = WP_2FA_URL . 'assets/images/';
$default_link_item = array(
'class' => '',
'href' => '#',
'target' => '_blank',
'label' => '',
'icon' => '',
'data' => '',
);
$out .= '<div id="mlp-overlay"></div>';
$out .= '<div id="mlp-flyout">';
$out .= '<a href="#" id="mlp-elmnts-button">';
$out .= '<span class="mlp-elmnts-label">Open Quick Links</span>';
$out .= '<span id="mlp-elmnts-image-wrapper">';
$out .= '<img src="' . esc_url( $icons_url . self::$config['icon_image'] ) . '" alt="Open Quick Links" title="Open Quick Links">';
$out .= '</span>';
$out .= '</a>';
$out .= '<div id="mlp-elmnts-menu">';
$i = 0;
foreach ( array_reverse( self::$config['menu_items'] ) as $item ) {
++$i;
$item = array_merge( $default_link_item, $item );
if ( ! empty( $item['icon'] ) && substr( $item['icon'], 0, 9 ) != 'dashicons' ) {
$item['class'] .= ' mlp-elmnts-custom-icon';
$item['class'] = trim( $item['class'] );
}
$out .= '<a ' . $item['data'] . ' href="' . esc_url( $item['href'] ) . '" class="mlp-elmnts-menu-item mlp-elmnts-menu-item-' . $i . ' ' . esc_attr( $item['class'] ) . '" target="_blank">';
$out .= '<span class="mlp-elmnts-label visible">' . esc_html( $item['label'] ) . '</span>';
if ( substr( $item['icon'], 0, 9 ) == 'dashicons' ) {
$out .= '<span class="dashicons ' . sanitize_text_field( $item['icon'] ) . '"></span>';
} elseif ( ! empty( $item['icon'] ) ) {
$out .= '<span class="mlp-elmnts-icon"><img src="' . esc_url( $icons_url . $item['icon'] ) . '"></span>';
}
$out .= '</a>';
} // foreach
$out .= '</div>'; // #mlp-elmnts-menu
$out .= '</div>'; // #mlp-flyout
echo $out; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Reads the config file remotely and sets 2 days transient for caching. If for some reason cant read the remote - false is returned
*
* @return bool|array
*
* @since 2.8.0
*/
public static function read_remote_config() {
$config = \get_transient( self::CONFIG_TRANSIENT_NAME );
if ( false === $config || empty( $config ) ) {
$api_response = \wp_remote_request( 'https://melapress.com/downloads/plugins-files/wp-2fa-flyout-config.php', array() );
$response_code = \wp_remote_retrieve_response_code( $api_response );
if ( \is_wp_error( $api_response ) || 200 !== (int) $response_code ) {
return false;
} else {
$config = \wp_remote_retrieve_body( $api_response );
\set_transient( self::CONFIG_TRANSIENT_NAME, $config, \DAY_IN_SECONDS * 3 );
return \json_decode( $config, true );
}
}
$config = json_decode( $config, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
$config = false;
}
return $config;
}
}
}
includes/classes/Admin/Fly-Out/index.php 0000644 00000000046 15075513060 0014160 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/Controllers/class-settings.php 0000644 00000035261 15075513060 0017032 0 ustar 00 <?php
/**
* Responsible for the plugin settings iterations
*
* @package wp2fa
* @subpackage admin_controllers
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
* @since 2.2.0
*/
declare(strict_types=1);
namespace WP2FA\Admin\Controllers;
use WP2FA\WP2FA;
use WP2FA\Admin\Settings_Page;
use WP2FA\Methods\Backup_Codes;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Extensions\OutOfBand\Out_Of_Band;
use WP2FA\Admin\SettingsPages\Settings_Page_Policies;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
if ( ! class_exists( '\WP2FA\Admin\Controllers\Settings' ) ) {
/**
* WP2FA Settings controller
*
* @since 2.2.0
*/
class Settings {
/**
* The link to the WP admin settings page
*
* @var string
*
* @since 2.2.0
*/
private static $settings_page_link = '';
/**
* The name of the WP2FA WP admin setup page
*
* @var string
*
* @since 2.2.0
*/
private static $setup_page_name = 'wp-2fa-setup';
/**
* The link to the WP admin setup page
*
* @var string
*
* @since 2.2.0
*/
private static $setup_page_link = '';
/**
* The link to the custom settings page (if one is presented)
*
* @var string
*
* @since 2.2.0
*/
private static $custom_setup_page_link = null;
/**
* Array with all the backup methods available.
*
* Array must contain the following:
* [backup_method_slug] - [
* 'wizard-step' - The name (HTML friendly as it will be used in the tags) of the plugin wizard step.
* 'button_name' - The button name shown in the wizard - language translated.
* ]
*
* @var array
*
* @since 2.0.0
*/
private static $backup_methods = null;
/**
* All available providers for the plugin
* For the specific role @see get_all_providers_for_role()
*
* @var array
*
* @since 2.2.0
*/
private static $all_providers = array();
/**
* All available providers for the plugin with their translated names.
*
* @var array
*
* @since 2.5.0
*/
private static $all_providers_names_translated = array();
/**
* All the available providers by user roles
*
* @var array
*
* @since 2.2.0
*/
private static $all_providers_for_roles = array();
/**
* Returns the link to the WP admin settings page, based on the current WP install
*
* @return string
*
* @since 2.2.0
*/
public static function get_settings_page_link() {
if ( '' === self::$settings_page_link ) {
self::$settings_page_link = add_query_arg( 'page', Settings_Page::TOP_MENU_SLUG, network_admin_url( 'admin.php' ) );
}
return self::$settings_page_link;
}
/**
* Returns the link to the WP admin settings page, based on the current WP install
*
* @return string
*
* @since 2.2.0
*/
public static function get_setup_page_link() {
if ( '' === self::$setup_page_link ) {
self::$setup_page_link = self::get_custom_page_link();
if ( empty( self::$setup_page_link ) ) {
if ( WP_Helper::is_multisite() ) {
self::$setup_page_link = add_query_arg( 'show', self::$setup_page_name, get_admin_url( get_current_blog_id(), 'profile.php' ) );
} else {
self::$setup_page_link = add_query_arg( 'show', self::$setup_page_name, admin_url( 'profile.php' ) );
}
}
}
return apply_filters( WP_2FA_PREFIX . 'setup_page_link', self::$setup_page_link );
}
/**
* Extracts the custom settings page URL
*
* @param mixed $user - User for which to extract the setting, null, \WP_User or user id - @see get_role_or_default_setting method of this class.
*
* @return string
*
* @since 2.2.0
*/
public static function get_custom_page_link( $user = null ): string {
if ( null === self::$custom_setup_page_link ) {
self::$custom_setup_page_link = self::get_role_or_default_setting( 'custom-user-page-id', $user );
if ( ! empty( self::$custom_setup_page_link ) ) {
$custom_slug = '';
if ( WP_Helper::is_multisite() ) {
\switch_to_blog( get_main_site_id() );
// $custom_slug = get_post_field( 'post_name', get_post( self::$custom_setup_page_link ) );
$new_page_permalink = get_permalink( get_post( self::$custom_setup_page_link ) );
self::$custom_setup_page_link = $new_page_permalink;//trailingslashit( get_site_url() ) . $custom_slug;
\restore_current_blog();
} else {
//$custom_slug = get_post_field( 'post_name', get_post( self::$custom_setup_page_link ) );
$new_page_permalink = get_permalink( get_post( self::$custom_setup_page_link ) );
self::$custom_setup_page_link = $new_page_permalink;
}
} else {
$custom_user_page_id = (int) self::get_custom_settings_page_id( '', $user );
if ( ! empty( $custom_user_page_id ) ) {
self::$custom_setup_page_link = \get_permalink( $custom_user_page_id );
}
}
}
return (string) \apply_filters( WP_2FA_PREFIX . 'custom_setup_page_link', self::$custom_setup_page_link, $user );
}
/**
* Check all the roles for given setting
*
* @param string $setting_name - The name of the setting to check for.
*
* @return boolean
*
* @since 2.0.0
*/
public static function check_setting_in_all_roles( string $setting_name ): bool {
$roles = WP_Helper::get_roles();
foreach ( $roles as $role ) {
if ( ! empty( WP2FA::get_wp2fa_setting( $setting_name, false, false, $role ) ) ) {
return true;
}
}
return false;
}
/**
* Return setting specific for the given role or default setting (based on user)
*
* @param string $setting_name - The name of the setting.
* @param mixed $user - \WP_User or any string or null - if string the current user will be used, if null global plugin setting will be used.
* @param mixed $role - The name of the role (or null).
* @param boolean $get_default_on_empty - Get default setting on empty setting value.
* @param boolean $get_default_value - Extracts default value.
*
* @return mixed
*
* @since 2.0.0
*/
public static function get_role_or_default_setting( string $setting_name, $user = null, $role = null, $get_default_on_empty = false, $get_default_value = false ) {
if ( null === $role ) {
/**
* No user specified - get the default settings
*/
if ( null === $user || \WP_2FA_PREFIX . 'no-user' === $user ) {
return WP2FA::get_wp2fa_setting( $setting_name, $get_default_on_empty, $get_default_value );
}
/**
* There is an User - extract the role
*/
if ( $user instanceof \WP_User || is_int( $user ) ) {
if ( null === $role ) {
$role = User_Helper::get_user_role( $user );
}
return WP2FA::get_wp2fa_setting( $setting_name, $get_default_on_empty, $get_default_value, $role );
}
/**
* No logged in current user, ergo no roles - fall back to defaults
*/
if ( 0 === User_Helper::get_user_object()->ID ) {
return WP2FA::get_wp2fa_setting( $setting_name, $get_default_on_empty, $get_default_value );
}
$role = User_Helper::get_user_role();
}
return WP2FA::get_wp2fa_setting( $setting_name, $get_default_on_empty, $get_default_value, $role );
}
/**
* Returns all the backup methods currently supported
*
* @return array
*
* @since 2.0.0
*/
public static function get_backup_methods(): array {
if ( null === self::$backup_methods ) {
/**
* Gives the ability to add additional backup methods
*
* @param array The array with all the backup methods currently supported.
*
* @since 2.0.0
*/
self::$backup_methods = apply_filters( WP_2FA_PREFIX . 'backup_methods_list', array() );
}
return self::$backup_methods;
}
/**
* Get backup methods enabled for user based on its role
*
* @param \WP_User $user - The WP user which we must check.
*
* @return array
*
* @since 2.0.0
*/
public static function get_enabled_backup_methods_for_user_role( \WP_User $user ): array {
$backup_methods = self::get_backup_methods();
/**
* Extensions could change the enabled backup methods array.
*
* @param array - Backup methods array.
* @param \WP_User - The user to check for.
*
* @since 2.0.0
*/
return apply_filters( WP_2FA_PREFIX . 'backup_methods_enabled', $backup_methods, $user );
}
/**
* Returns all enabled providers for specific role
*
* @param string $role - The name of the role to check for.
*
* @return array
*
* @throws \Exception - if the role is wrong - throws an exception.
*
* @since 2.2.0
*/
public static function get_enabled_providers_for_role( string $role ) {
if ( WP_Helper::is_role_exists( $role ) ) {
self::get_all_roles_providers();
return self::$all_providers_for_roles[ $role ];
} elseif ( '' === $role ) {
return array();
} else {
throw new \Exception( 'Role provided does not exists - "' . $role . '"' ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
}
/**
* Checks if given provider is enabled for the given role.
*
* @param string $role - The name of the role.
* @param string $provider - The name of the provider.
*
* @return boolean
*
* @throws \Exception - If the provider is not registered in the plugin.
*
* @since 2.2.0
*/
public static function is_provider_enabled_for_role( string $role, string $provider ): bool {
self::get_providers();
if ( in_array( $provider, self::$all_providers, true ) ) {
self::get_enabled_providers_for_role( $role );
if ( isset( self::$all_providers_for_roles[ $role ][ $provider ] ) ) {
return true;
}
return false;
}
throw new \Exception( 'Non existing provider ' . $provider ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
/**
* Returns all providers by roles.
* If given role does not have specified settings set - falls back to the default settings.
*
* @return array
*
* @since 2.2.0
*/
public static function get_all_roles_providers() {
if ( empty( self::$all_providers_for_roles ) ) {
$roles = WP_Helper::get_roles();
$providers = self::get_providers();
foreach ( $roles as $role ) {
self::$all_providers_for_roles[ $role ] = array();
foreach ( $providers as $provider ) {
if ( Backup_Codes::METHOD_NAME === $provider ) {
self::$all_providers_for_roles[ $role ][ $provider ] = WP2FA::get_wp2fa_setting( $provider . '_enabled', false, false, $role );
} elseif ( 'backup_email' === $provider ) {
self::$all_providers_for_roles[ $role ][ $provider ] = WP2FA::get_wp2fa_setting( 'enable-email-backup', false, false, $role );
} elseif ( class_exists( '\WP2FA\Extensions\OutOfBand\Out_Of_Band', false ) && Out_Of_Band::METHOD_NAME === $provider ) {
self::$all_providers_for_roles[ $role ][ $provider ] = WP2FA::get_wp2fa_setting( 'enable_' . $provider . '_email', false, false, $role );
} else {
self::$all_providers_for_roles[ $role ][ $provider ] = WP2FA::get_wp2fa_setting( 'enable_' . $provider, false, false, $role );
}
}
self::$all_providers_for_roles[ $role ] = array_filter( self::$all_providers_for_roles[ $role ] );
}
}
return self::$all_providers_for_roles;
}
/**
* Returns an array with all providers and their translated name. Key is the method slug and value is the translated method name.
*
* @return array
*
* @since 2.5.0
*/
public static function get_providers_translate_names(): array {
if ( empty( self::$all_providers_names_translated ) ) {
/**
* Filter the supplied providers.
*
* This lets third-parties either remove providers (such as Email), or
* add their own providers (such as text message or Clef).
*
* @param array $provider array if available options.
*/
self::$all_providers_names_translated = apply_filters( WP_2FA_PREFIX . 'providers_translated_names', self::$all_providers_names_translated );
}
return self::$all_providers_names_translated;
}
/**
* Grab list of all register providers in the plugin.
*
* @return array
*
* @since 2.2.0
*/
public static function get_providers() {
if ( empty( self::$all_providers ) ) {
/**
* Filter the supplied providers.
*
* This lets third-parties either remove providers (such as Email), or
* add their own providers (such as text message or Clef).
*
* @param array $provider array if available options.
*/
self::$all_providers = apply_filters( WP_2FA_PREFIX . 'providers', self::$all_providers );
}
return self::$all_providers;
}
/**
* Returns the page ID stored in the given role or user, based on the multisite and page URL only.
*
* @param string $role - The role name if any. Default fallback if not role no user is provided.
* @param \WP_User|int $user - The user object or user id if any.
*
* @return int
*
* @since 2.5.0
*/
public static function get_custom_settings_page_id( $role = '', $user = '' ) {
if ( ! empty( $role ) ) {
$page_slug = self::get_role_or_default_setting( 'custom-user-page-url', '', $role );
} elseif ( ! empty( $user ) ) {
$page_slug = self::get_role_or_default_setting( 'custom-user-page-url', $user );
} else {
$page_slug = self::get_role_or_default_setting( 'custom-user-page-url', '', '' );
}
if ( ! empty( $role ) ) {
$separate_page = self::get_role_or_default_setting( 'separate-multisite-page-url', '', $role );
} elseif ( ! empty( $user ) ) {
$separate_page = self::get_role_or_default_setting( 'separate-multisite-page-url', $user );
} else {
$separate_page = self::get_role_or_default_setting( 'separate-multisite-page-url', '', '' );
}
$new_page_id = '';
// Lets check for multisite first and if that is the case - lets search for that page on the user's default blog.
if ( WP_Helper::is_multisite() && false !== $separate_page ) {
if ( ! empty( $user ) ) {
$blog_id = User_Helper::get_user_default_blog( $user );
} else {
$blog_id = \get_current_blog_id();
}
if ( 0 === $blog_id ) {
$new_page_id = '';
} else {
// Switch to the blog context.
\switch_to_blog( $blog_id );
$page_exists = Settings_Page_Policies::get_post_by_post_name( $page_slug, 'page' );
// Restore global context.
\restore_current_blog();
if ( false !== $page_exists ) {
$new_page_id = $page_exists->ID;
}
}
} else {
$page_exists = Settings_Page_Policies::get_post_by_post_name( $page_slug, 'page' );
if ( false !== $page_exists ) {
$new_page_id = $page_exists->ID;
}
}
return $new_page_id;
}
}
}
includes/classes/Admin/Controllers/index.php 0000644 00000000046 15075513060 0015167 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/Controllers/class-methods.php 0000644 00000006442 15075513060 0016634 0 ustar 00 <?php
/**
* Responsible for the plugin methods
*
* @package wp2fa
* @subpackage admin_controllers
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Controllers;
use WP2FA\WP2FA;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Helpers\Methods_Helper;
use WP2FA\Extensions\OutOfBand\Out_Of_Band;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* Methods class
*/
if ( ! class_exists( '\WP2FA\Admin\Controllers\Methods' ) ) {
/**
* All the methods related functionality must be extracted from this class. Responsible only for global methods data, not the user method related stuff.
*
* @since 2.2.0
*/
class Methods {
/**
* Holds all the enabled methods in the plugin
*
* @var array
*
* @since 2.2.0
*/
private static $enabled_methods = null;
/**
* Works our a list of available 2FA methods. It doesn't include the disabled ones.
*
* TODO: There is a high possibility that this method is duplication of the Settings::get_providers - check and make the changes as there must be only one way to extract that info
*
* @return string[]
* @since 2.0.0
*/
public static function get_available_2fa_methods(): array {
$available_methods = array();
/**
* Add an option for external providers to implement their own 2fa methods and set them as available.
*
* @param array $available_methods - The array with all the available methods.
*
* @since 2.0.0
*/
return \apply_filters( WP_2FA_PREFIX . 'available_2fa_methods', $available_methods );
}
/**
* Returns array with all the enabled methods in the plugin for the current role
*
* @param string $role - Role to extract data for.
*
* @return array
*
* @since 2.2.0
*/
public static function get_enabled_methods( $role = 'global' ): array {
if ( null === self::$enabled_methods || ! isset( self::$enabled_methods[ $role ] ) ) {
self::$enabled_methods[ $role ] = array();
$providers = Settings::get_providers();
foreach ( $providers as $provider ) {
if ( Settings::is_provider_enabled_for_role( $role, $provider ) ) {
$method = Methods_Helper::get_method_by_provider_name( $provider );
if ( $method && \method_exists( $method, 'is_secondary' ) && $method::is_secondary() ) {
continue;
} elseif ( class_exists( '\WP2FA\Extensions\OutOfBand\Out_Of_Band', false ) && Out_Of_Band::METHOD_NAME === $provider ) {
self::$enabled_methods[ $role ][ $provider ] = WP2FA::get_wp2fa_setting( 'enable_' . $provider . '_email', false, false, $role );
} else {
self::$enabled_methods[ $role ][ $provider ] = WP2FA::get_wp2fa_setting( 'enable_' . $provider, false, false, $role );
}
}
}
self::$enabled_methods[ $role ] = array_filter( self::$enabled_methods[ $role ] );
}
return self::$enabled_methods;
}
/**
* Returns text with the number of methods supported for the given role
*
* @since 2.2.0
*
* @return string
*/
public static function get_number_of_methods_text() {
return esc_html__(
'There are {available_methods_count} methods available to choose from for 2FA:',
'wp-2fa'
);
}
}
}
includes/classes/Admin/class-premium-features.php 0000644 00000046301 15075513060 0016153 0 ustar 00 <?php
/**
* Premium features rendering class.
*
* @package wp2fa
* @subpackage admin
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
* @since 2.0.0
*/
declare(strict_types=1);
namespace WP2FA\Admin;
/*
* Premium_Features class for the premium features show
*
* @since 2.4.0
*/
if ( ! class_exists( '\WP2FA\Admin\Premium_Features' ) ) {
/**
* Handles contact the features page and content.
*/
class Premium_Features {
public const TOP_MENU_SLUG = 'wp-2fa-premium-features';
/**
* Create admin menu entry and settings page.
*
* @return void
*
* @since 2.8.0
*/
public static function add_extra_menu_item() {
\add_submenu_page(
Settings_Page::TOP_MENU_SLUG,
\esc_html__( 'Premium Features', 'wp-2fa' ),
\esc_html__( 'Premium Features ➤', 'wp-2fa' ),
'manage_options',
self::TOP_MENU_SLUG,
array( __CLASS__, 'render' ),
100
);
}
/**
* Adds an upgrade banner to settings pages.
*
* @return void
*
* @since 2.8.0
*/
public static function add_settings_banner() {
$banner = '<div id="wp-2fa-side-banner">';
$banner .= '<img src="' . \esc_url( WP_2FA_URL . 'dist/images/wizard-logo.png' ) . '">';
$banner .= '<p>' . \esc_html__( 'Upgrade to Premium & benefit:', 'wp-2fa' ) . '</p>';
$banner .= '<ul><li><span class="dashicons dashicons-yes-alt"></span>' . \esc_html__( 'Login with 2FA via SMS, push notification or with a simple mouse click', 'wp-2fa' ) . '</li>';
$banner .= '<li><span class="dashicons dashicons-yes-alt"></span>' . \esc_html__( 'Add & manage trusted devices ("Remember this device" option)', 'wp-2fa' ) . '</li>';
$banner .= '<li><span class="dashicons dashicons-yes-alt"></span> ' . \esc_html__( 'Add alternative 2FA methods ensuring no user is ever locked out', 'wp-2fa' ) . '</li>';
$banner .= '<li><span class="dashicons dashicons-yes-alt"></span> ' . \esc_html__( 'One-click 2FA integration with WooCommerce', 'wp-2fa' ) . '</li>';
$banner .= '<li><span class="dashicons dashicons-yes-alt"></span> ' . \esc_html__( 'Completely whitelabel the 2FA user experience including the 2FA code page, email & wizards text', 'wp-2fa' ) . '</li>';
$banner .= '<li><span class="dashicons dashicons-yes-alt"></span> ' . \esc_html__( 'Configure different 2FA policies for different user roles', 'wp-2fa' ) . '</li>';
$banner .= '<li><span class="dashicons dashicons-yes-alt"></span> ' . \esc_html__( 'Many other features', 'wp-2fa' ) . '</li>';
$banner .= '<li><span class="dashicons dashicons-yes-alt"></span> ' . \esc_html__( 'No Ads!', 'wp-2fa' ) . '</li></ul>';
$banner .= '<a href="https://melapress.com/wordpress-2fa/pricing/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" class="button button-primary" target="_blank">' . \esc_html__( 'Upgrade to Premium', 'wp-2fa' ) . '</a>';
$banner .= '</div>';
echo $banner; // phpcs:ignore
}
/**
* Handles rendering the content.
*
* @return void
*
* @since 2.8.0
*/
public static function render() {
?>
<style>
.features-wrap {
background: #fff;
padding: 25px 30px;
margin-top: 25px;
}
.features-wrap h2 {
font-size: 28px;
margin-bottom: 30px;
}
.features-wrap p {
font-size: 16px;
line-height: 28px;
}
.feature-list {
margin-bottom: 20px;
}
.feature-list li {
margin-bottom: 10px;
font-size: 15px;
}
.feature-list li .dashicons {
color: #3E6BFF;
}
.premium-cta {
margin: 25px 0 15px;
text-align: center;
}
.premium-cta a:not(.inverse), .table-link {
background-color: #3E6BFF;
color: #fff;
padding: 15px 26px;
border-radius: 30px;
font-size: 16px;
white-space: nowrap;
text-decoration: none;
font-weight: 700;
display: inline-block;
margin-right: 15px;
border: 2px solid #3E6BFF;
}
.premium-cta a:hover, .table-link:hover, .premium-cta a.inverse, .table-link.inverse {
color: #3E6BFF;
background-color: #fff;
}
.premium-cta a.inverse {
font-weight: 700;
text-decoration: none;
font-size: 16px;
}
.content-block {
margin-bottom: 26px;
border-bottom: 1px solid #eee;
padding-bottom: 15px;
}
.feature-table tr td {
text-align: center;
min-width: 200px
}
.feature-table tr td:first-of-type {
text-align: left;
font-weight: 500;
}
.feature-table td p {
margin-top: 0;
}
.row-head span {
font-size: 17px;
font-weight: 700;
}
.feature-table .dashicons {
color: #3E6BFF;
}
.feature-table .dashicons-no {
color: red;
}
.table-link {
font-size: 14px;
padding: 9px;
width: 193px;
margin-top: 10px;
}
.pull-up {
position: relative;
top: -23px;
}
.wp2fa-logo {
max-width: 130px;
}
.logo-wrap {
float: left;
margin-right: 30px;
}
</style>
<div class="wrap help-wrap features-wrap wp-2fa-settings-wrapper">
<div class="page-head">
<h2><?php \esc_html_e( 'Upgrade to Premium and benefit more!', 'wp-2fa' ); ?></h2>
</div>
<div class="content-block">
<div class="logo-wrap">
<img class="wp2fa-logo" src="<?php echo WP_2FA_URL; // phpcs:ignore?>dist/images/wp-2fa-color_opt.png" alt="">
</div>
<div>
<p><?php \esc_html_e( 'WP 2FA is your trusted gatekeeper, keeping your website, users, customers, team members, and anyone who accesses your website, including you, secure and better protected than ever before.', 'wp-2fa' ); ?></p>
<p><?php \esc_html_e( 'Upgrade to WP 2FA Premium to add more secure authentication options and automate more, encouraging all your website users to utilize 2FA to its fullest extent and give your users more flexibility by allowing them to work from anywhere without compromising on security.', 'wp-2fa' ); ?></p>
</div>
</div>
<div class="content-block">
<p><strong><?php \esc_html_e( 'Upgrade to Premium and start benefiting from value-added features such as:', 'wp-2fa' ); ?></strong></p>
<ul class="feature-list">
<li><span class="dashicons dashicons-saved"></span> <?php \esc_html_e( 'More 2FA methods, including SMS, push notifications & one-click login', 'wp-2fa' ); ?></li>
<li><span class="dashicons dashicons-saved"></span> <?php \esc_html_e( 'Trusted devices: Allow users to add trusted devices so they do not have to manually enter the 2FA code each time they log in', 'wp-2fa' ); ?></li>
<li><span class="dashicons dashicons-saved"></span> <?php \esc_html_e( 'White labeling features: Gain increased trust by extending your business’ branding and tone of voice to all 2FA pages, wizards & emails', 'wp-2fa' ); ?></li>
<li><span class="dashicons dashicons-saved"></span> <?php \esc_html_e( 'Refer to the features matrix below for a detailed list of all the premium features', 'wp-2fa' ); ?></li>
</ul>
<div class="premium-cta">
<a href="<?php echo \esc_url( 'https://melapress.com/wordpress-2fa/pricing/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ); ?>" target="_blank" rel="noopener"><?php \esc_html_e( 'Upgrade to Premium', 'wp-2fa' ); ?></a>
</div>
</div>
<div class="content-block">
<p><strong><?php \esc_html_e( 'WP 2FA plugin features', 'wp-2fa' ); ?></strong></p>
<p><?php \esc_html_e( 'Take advantage of these benefits and many others, with prices starting from as little as $29 for 5 users per year. ', 'wp-2fa' ); ?></p>
<table class="c21 feature-table">
<tbody>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10 c4"><span class="c5"></span></p>
</td>
<td class="c8 row-head" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><?php \esc_html_e( 'Premium', 'wp-2fa' ); ?></span></p>
</td>
<td class="c12 row-head" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><?php \esc_html_e( 'Free', 'wp-2fa' ); ?></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Support', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><?php \esc_html_e( '1-to-1 emails, forums', 'wp-2fa' ); ?></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><?php \esc_html_e( 'forums', 'wp-2fa' ); ?></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Out of the box support for e-commerce, membership & third party plugins (no code required)', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( '2FA code via mobile app', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( '2FA code over email', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( '2FA login with hardware key (YubiKey)', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( '2FA login with push notification (Authy)', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( '2FA Login with SMS (with Twilio or Clickatell)', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'One-click 2FA login (via link in email)', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Different 2FA policies per user role', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Trusted devices (remember devices)', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Alternative 2FA methods', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><?php \esc_html_e( 'Backup codes only', 'wp-2fa' ); ?></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'White labeling (logo, wizards, email, colours, fonts & custom CSS)', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'One-click 2FA integration in WooCommerce user page', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Reports & Statistics', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Configurable 2FA code expiration time', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Sortable users\' 2FA status', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'Export/import plugin settings', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
<tr class="c2">
<td class="c6" colspan="1" rowspan="1">
<p class="c10"><span class="c5"><?php \esc_html_e( 'No Ads!', 'wp-2fa' ); ?></span></p>
</td>
<td class="c8" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-saved"></span></span></p>
</td>
<td class="c12" colspan="1" rowspan="1">
<p class="c7"><span class="c5"><span class="dashicons dashicons-no"></span></span></p>
</td>
</tr>
</tbody>
</table>
<div class="premium-cta">
<a href="<?php echo \esc_url( 'https://melapress.com/wordpress-2fa/pricing/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ); ?>" target="_blank" rel="noopener"><?php \esc_html_e( 'Upgrade to Premium', 'wp-2fa' ); ?></a>
</div>
</div>
<div>
<p>
<?php
$text = sprintf(
/* translators: 1: Link to our site 2: Link to our contact page */
\esc_html__( 'Visit the WP 2FA %1$s for more information or %2$s with any questions you might have. We look forward to hearing from you.', 'wp-2fa' ),
'<a target="_blank" href="' . \esc_url( 'https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ) . '">' . \esc_html__( 'plugin website', 'wp-2fa' ) . '</a>',
'<a target="_blank" href="' . \esc_url( 'https://melapress.com/contact/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ) . '">' . \esc_html__( 'contact us', 'wp-2fa' ) . '</a>'
);
echo $text; // phpcs:ignore -- Visit the WP 2FA plugin website for more information or contact us with any questions you might have. We look forward to hearing from you.
?>
</p>
</div>
</div>
<?php
}
/**
* Add "_blank" attr to pricing link to ensure it opens in new tab.
*
* @return void
*
* @since 2.8.0
*/
public static function pricing_new_tab_js() {
?>
<script type="text/javascript">
jQuery( document ).ready( function() {
jQuery( '.wp-2fa.pricing' ).parent().attr( 'target', '_blank' );
});
</script>
<?php
}
}
}
includes/classes/Admin/Views/class-wizard-steps.php 0000644 00000043361 15075513060 0016415 0 ustar 00 <?php
/**
* Settings page render class.
*
* @package wp2fa
* @subpackage views
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Views;
use WP2FA\WP2FA;
use WP2FA\Utils\User_Utils;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
if ( ! class_exists( '\WP2FA\Admin\Views\Wizard_Steps' ) ) {
/**
* WP2FA Wizard Settings view controller
*
* @since 1.7
*/
class Wizard_Steps {
/**
* Holds the nonce for json calls
*
* @since 1.7
*
* @var string
*/
private static $json_nonce = null;
/**
* Holds the url to which to redirect the user after the setup is finished
*
* @var string
*
* @since 2.0.0
*/
private static $redirect_url = null;
/**
* Introduction step form
*
* @since 1.7
*
* @return void
*/
public static function optional_user_welcome_step() {
?>
<div class="wizard-step active">
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'welcome', true ) ); ?>
</div>
<div class="wp2fa-setup-actions">
<a href="#" class="button wp-2fa-button-primary button-primary" data-name="next_step_setting_modal_wizard" data-next-step="choose-2fa-method"><?php \esc_html_e( 'Next Step', 'wp-2fa' ); ?></a>
<button class="wp-2fa-button-secondary button button-secondary wp-2fa-button-secondary" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
</div>
</div>
<?php
}
/**
* Introduction step form
*
* @since 1.7
*
* @return void
*/
public static function introduction_step() {
?>
<form method="post" class="wp2fa-setup-form">
<?php wp_nonce_field( 'wp2fa-step-addon' ); ?>
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( '2fa_required_intro', true ) ); ?>
</div>
<div class="wp2fa-setup-actions">
<button class="button button-primary wp-2fa-button-primary"
type="submit"
name="save_step"
value="<?php \esc_attr_e( 'Next', 'wp-2fa' ); ?>">
<?php \esc_html_e( 'Next', 'wp-2fa' ); ?>
</button>
</div>
</form>
<?php
}
/**
* Welcome step of the wizard
*
* @since 1.7
*
* @param string $next_step - url of the next step.
*
* @return void
*/
public static function welcome_step( $next_step ) {
$redirect = Settings::get_settings_page_link();
?>
<h3><?php \esc_html_e( 'Let us help you get started', 'wp-2fa' ); ?></h3>
<p><?php \esc_html_e( 'Thank you for installing the WP 2FA plugin. This quick wizard will assist you with configuring the plugin and the two-factor authentication (2FA) settings for your user and the users on this website.', 'wp-2fa' ); ?></p>
<div class="wp2fa-setup-actions">
<a class="button button-primary"
href="<?php echo \esc_url( $next_step ); ?>">
<?php \esc_html_e( 'Let’s get started!', 'wp-2fa' ); ?>
</a>
<a class="button button-secondary wp-2fa-button-secondary first-time-wizard"
href="<?php echo \esc_url( $redirect ); ?>">
<?php \esc_html_e( 'Skip Wizard - I know how to do this', 'wp-2fa' ); ?>
</a>
</div>
<?php
}
/**
* Configure backup codes step
*
* @since 1.7
*
* @return void
*/
public static function backup_codes_configure() {
$user_type = User_Utils::determine_user_2fa_status( User_Helper::get_user_object() );
$redirect = self::determine_redirect_url();
?>
<div class="step-setting-wrapper active">
<?php
if ( in_array( 'user_needs_to_setup_backup_codes', $user_type, true ) ) {
?>
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'backup_codes_intro', true ) ); ?>
</div>
<?php } else { ?>
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'backup_codes_intro_continue', true ) ); ?>
</div>
<?php } ?>
<div class="wp2fa-setup-actions">
<?php if ( in_array( 'user_needs_to_setup_backup_codes', $user_type, true ) ) { ?>
<button class="button button-primary wp-2fa-button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'Generate backup codes', 'wp-2fa' ); ?>" data-trigger-generate-backup-codes <?php echo WP_Helper::create_data_nonce( self::json_nonce() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php \esc_html_e( 'Generate list of backup codes', 'wp-2fa' ); ?>
</button>
<?php
if ( ! empty( $redirect ) ) {
?>
<a href="<?php echo \esc_url( $redirect ); ?>" class="button button-secondary wp-2fa-button-secondary wp-2fa-button-secondary close-first-time-wizard">
<?php \esc_html_e( 'I’ll generate them later', 'wp-2fa' ); ?>
</a>
<?php
} else {
?>
<a href="#" class="button wp-2fa-button-secondary" data-close-2fa-modal value="<?php \esc_attr_e( 'I’ll generate them later', 'wp-2fa' ); ?>">
<?php \esc_html_e( 'I’ll generate them later', 'wp-2fa' ); ?>
</a>
<?php } ?>
<?php } else { ?>
<?php
if ( ! empty( $redirect ) ) {
?>
<a href="<?php echo \esc_url( $redirect ); ?>" class="button button-secondary wp-2fa-button-secondary close-first-time-wizard">
<?php \esc_html_e( 'Close wizard', 'wp-2fa' ); ?>
</a>
<?php
} else {
?>
<a href="#" class="button button-secondary wp-2fa-button-secondary" data-reload>
<?php \esc_html_e( 'Close wizard', 'wp-2fa' ); ?>
</a>
<?php } ?>
<?php } ?>
</div>
</div>
<?php
}
/**
* Generate backup codes step
*
* @since 1.7
*
* @return void
*/
public static function generate_backup_codes() {
?>
<div class="step-setting-wrapper active" data-step-title="<?php \esc_html_e( 'Generate codes', 'wp-2fa' ); ?>">
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'backup_codes_generate_intro', true ) ); ?>
</div>
<div class="wp2fa-setup-actions">
<button class="button button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'Generate backup codes', 'wp-2fa' ); ?>" data-trigger-generate-backup-codes <?php echo WP_Helper::create_data_nonce( self::json_nonce() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php \esc_html_e( 'Generate list of backup codes', 'wp-2fa' ); ?>
</button>
<a href="#" class="button button-secondary wp-2fa-button-secondary" value="<?php \esc_attr_e( 'I’ll generate them later', 'wp-2fa' ); ?>" data-close-2fa-modal="">
<?php \esc_html_e( 'I’ll generate them later', 'wp-2fa' ); ?>
</a>
</div>
</div>
<?php
}
/**
* Creates link for generating the backup codes
*
* @since 1.7
*
* @return string
*/
public static function get_generate_codes_label() {
$label = __( 'Backup 2FA methods:', 'wp-2fa' );
return $label . '</th><td>';
}
/**
* Creates backup codes URL link
*
* @return string
*
* @since 2.6.0
*/
public static function get_backup_codes_link(): string {
return '<a href="#" class="button button-primary remove-2fa" data-trigger-generate-backup-codes ' . WP_Helper::create_data_nonce( self::json_nonce() ) . ' onclick="MicroModal.show( \'configure-2fa-backup-codes\' );">' . __( 'Generate list of backup codes', 'wp-2fa' ) . '</a>';
}
/**
* Shows the wrapper where backup code are generated and showed to the user
*
* @param boolean $backup_only - If we want to show backup window only - sets the class of the div to active.
*
* @since 1.7
*
* @return void
*/
public static function generated_backup_codes( $backup_only = false ) {
$redirect = self::determine_redirect_url();
?>
<div class="step-setting-wrapper align-center<?php echo ( $backup_only ) ? ' active' : ''; ?>" data-step-title="<?php \esc_html_e( 'Your backup codes', 'wp-2fa' ); ?>">
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'backup_codes_generated', true ) ); ?>
</div>
<div class="backup-key-wrapper">
<textarea id="backup-codes-wrapper" readonly rows="4" cols="50" class="app-key"></textarea>
</div>
<div class="wp2fa-setup-actions">
<?php if ( is_ssl() ) { ?>
<button class="button button-primary wp-2fa-button-primary" type="submit" value="<?php \esc_attr_e( 'Download', 'wp-2fa' ); ?>" data-trigger-backup-code-copy>
<?php \esc_html_e( 'Copy', 'wp-2fa' ); ?>
</button>
<?php } else { ?>
<button class="button button-primary wp-2fa-button-primary" type="submit" value="<?php \esc_attr_e( 'Download', 'wp-2fa' ); ?>" data-trigger-backup-code-download data-user="<?php echo \esc_attr( User_Helper::get_user_object()->display_name ); ?>" data-website-url="<?php echo \esc_attr( get_home_url() ); ?>">
<?php \esc_html_e( 'Download', 'wp-2fa' ); ?>
</button>
<?php } ?>
<button class="button button-primary wp-2fa-button-primary" type="submit" value="<?php \esc_attr_e( 'Print', 'wp-2fa' ); ?>" data-trigger-print <?php echo WP_Helper::create_data_nonce( self::json_nonce() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->display_name ); ?>" data-website-url="<?php echo \esc_attr( get_home_url() ); ?>">
<?php \esc_html_e( 'Print', 'wp-2fa' ); ?>
</button>
<button class="button button-primary wp-2fa-button-primary" type="submit" value="<?php \esc_attr_e( 'Send me the codes via email', 'wp-2fa' ); ?>" data-trigger-backup-code-email <?php echo WP_Helper::create_data_nonce( 'wp-2fa-send-backup-codes-email-nonce' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" data-website-url="<?php echo \esc_attr( get_home_url() ); ?>">
<?php \esc_html_e( 'Send me the codes via email', 'wp-2fa' ); ?>
</button>
<?php
if ( ! empty( $redirect ) ) {
?>
<a href="<?php echo \esc_url( $redirect ); ?>" class="button button-secondary wp-2fa-button-secondary wp-2fa-button-secondary close-first-time-wizard">
<?php \esc_html_e( 'I\'m ready, close the wizard', 'wp-2fa' ); ?>
</a>
<?php
} else {
?>
<button class="button button-secondary wp-2fa-button-secondary wp-2fa-button-secondary" type="submit" data-close-2fa-modal-and-refresh>
<?php \esc_html_e( 'I\'m ready, close the wizard', 'wp-2fa' ); ?>
</button>
<?php } ?>
</div>
</div>
<?php
}
/**
* Final step for congratulating the user
*
* @since 1.7
*
* @param boolean $setup_wizard - Is that a call from setup wizard or not.
*
* @return void
*/
public static function congratulations_step( $setup_wizard = false ) {
if ( $setup_wizard ) {
self::congratulations_step_plugin_wizard();
return;
}
$redirect = ( '' !== self::determine_redirect_url() ) ? self::determine_redirect_url() : '';
?>
<div class="step-setting-wrapper active">
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'no_further_action', true ) ); ?>
</div>
<div class="wp2fa-setup-actions">
<?php if ( '' !== trim( $redirect ) ) { ?>
<a href="<?php echo \esc_url( $redirect ); ?>" class="button button-secondary wp-2fa-button-secondary close-first-time-wizard">
<?php \esc_html_e( 'Close wizard', 'wp-2fa' ); ?>
</a>
<?php } else { ?>
<button class="modal__btn wp-2fa-button-secondary button" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Close wizard', 'wp-2fa' ); ?></button>
<?php } ?>
</div>
</div>
<?php
}
/**
* Final step for congratulating the user
*
* @since 1.7
*
* @return void
*/
public static function congratulations_step_plugin_wizard() {
$redirect = ( '' !== self::determine_redirect_url() ) ? self::determine_redirect_url() : get_edit_profile_url( User_Helper::get_user_object()->ID );
$slide_title = ( User_Helper::is_excluded( User_Helper::get_user_object()->ID ) ) ? \esc_html__( 'Congratulations.', 'wp-2fa' ) : \esc_html__( 'Congratulations, you\'re almost there...', 'wp-2fa' );
?>
<h3><?php echo \esc_html( $slide_title ); ?></h3>
<p><?php \esc_html_e( 'Great job, the plugin and 2FA policies are now configured. You can always change the plugin settings and 2FA policies at a later stage from the WP 2FA entry in the WordPress menu.', 'wp-2fa' ); ?></p>
<?php
if ( User_Helper::is_excluded( User_Helper::get_user_object()->ID ) ) {
?>
<div class="wp2fa-setup-actions">
<a href="<?php echo \esc_url( $redirect ); ?>" class="button button-secondary wp-2fa-button-secondary close-first-time-wizard">
<?php \esc_html_e( 'Close wizard', 'wp-2fa' ); ?>
</a>
</div>
<?php
} else {
?>
<p><?php \esc_html_e( 'Now you need to configure 2FA for your own user account. You can do this now (recommended) or later.', 'wp-2fa' ); ?></p>
<div class="wp2fa-setup-actions">
<a href="<?php echo \esc_url( Settings::get_setup_page_link() ); ?>" class="button button-primary wp-2fa-button-secondary">
<?php \esc_html_e( 'Configure 2FA now', 'wp-2fa' ); ?>
</a>
<a href="<?php echo \esc_url( Settings::get_settings_page_link() ); ?>" class="button button-secondary wp-2fa-button-secondary close-first-time-wizard">
<?php \esc_html_e( 'Close wizard & configure 2FA later', 'wp-2fa' ); ?>
</a>
</div>
<?php } ?>
<?php
}
/**
* Shows the methods in the modal wizard, so the user can choose from the available ones
*
* @return void
*/
public static function show_modal_methods() {
/**
* Add an option for external providers to add their own modal methods options.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'modal_methods' );
}
/**
* Choosing backup method step
* When there are more than one backup method - give the user ability to choose one
*
* @return void
*
* @since 2.0.0
*/
public static function choose_backup_method() {
$redirect = self::determine_redirect_url();
?>
<div class="wizard-step" id="2fa-wizard-backup-methods">
<div class="option-pill mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'backup_codes_intro_multi', true ) ); ?>
</div>
<div class="radio-cells">
<?php
$backup_methods = Settings::get_backup_methods();
$i = 0;
foreach ( $backup_methods as $method_name => $method ) {
$checked = '';
if ( ! $i ) {
$checked = ' checked="checked"';
}
$i = 1;
?>
<div class="option-pill"><label for="<?php echo \esc_attr( $method_name ); ?>"><input name="backup_method_select" data-step="<?php echo \esc_attr( $method['wizard-step'] ); ?>" type="radio" id="<?php echo \esc_attr( $method_name ); ?>" <?php echo $checked; ?>><?php echo $method['button_name']; // phpcs:ignore ?></label><br /></div>
<?php
}
?>
</div>
<div class="wp2fa-setup-actions">
<a id="select-backup-method" href="<?php echo \esc_url( Settings::get_setup_page_link() ); ?>" class="button button-primary wp-2fa-button-primary">
<?php \esc_html_e( 'Configure backup 2FA method', 'wp-2fa' ); ?>
</a>
<a href="<?php echo \esc_url( $redirect ); ?>" class="button button-secondary wp-2fa-button-secondary close-first-time-wizard" <?php echo ( ( '' === trim( (string) $redirect ) ) ? 'data-close-it=""' : '' ); ?> >
<?php \esc_html_e( 'Close wizard & configure 2FA later', 'wp-2fa' ); ?>
</a>
<script>
const closeButton = document.querySelector('[data-close-it]');
if (closeButton) {
closeButton.addEventListener('click', (event) => {
event.preventDefault();
let url = new URL( location.href );
let params = new URLSearchParams( url.search );
params.delete('show');
location.replace( `${location.pathname}?${params}` );
});
}
</script>
</div>
</div>
<?php
}
/**
* Determines the redirect url for the user
*
* @return string
*
* @since 2.0.0
*/
public static function determine_redirect_url(): string {
if ( null === self::$redirect_url ) {
$redirect_page = Settings::get_role_or_default_setting( 'redirect-user-custom-page-global', User_Helper::get_user_object() );
self::$redirect_url = ( '' !== trim( (string) $redirect_page ) ) ? \trailingslashit( get_site_url() ) . $redirect_page : '';
if (
'yes' === Settings::get_role_or_default_setting( 'create-custom-user-page', User_Helper::get_user_object() ) ||
'yes' === Settings::get_role_or_default_setting( 'create-custom-user-page' ) ) {
if (
'' !== trim( (string) Settings::get_role_or_default_setting( 'redirect-user-custom-page', User_Helper::get_user_object() ) ) ||
'' !== trim( (string) Settings::get_role_or_default_setting( 'redirect-user-custom-page' ) ) ) {
if ( 'yes' === Settings::get_role_or_default_setting( 'create-custom-user-page', User_Helper::get_user_object() ) ) {
self::$redirect_url = trailingslashit( get_site_url() ) . Settings::get_role_or_default_setting( 'redirect-user-custom-page', User_Helper::get_user_object() );
} else {
self::$redirect_url = trailingslashit( get_site_url() ) . Settings::get_role_or_default_setting( 'redirect-user-custom-page' );
}
}
}
}
return self::$redirect_url;
}
/**
* Generates nonce for JSON calls
*
* @since 1.7
*
* @return string
*/
protected static function json_nonce() {
if ( null === self::$json_nonce ) {
self::$json_nonce = 'wp-2fa-backup-codes-generate-json-' . User_Helper::get_user_object()->ID;
}
return self::$json_nonce;
}
}
}
includes/classes/Admin/Views/index.php 0000644 00000000046 15075513060 0013756 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/Views/class-first-time-wizard-steps.php 0000644 00000062007 15075513060 0020474 0 ustar 00 <?php
/**
* Settings page render class.
*
* @package wp2fa
* @subpackage views
* @since 1.7.0
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Views;
use WP2FA\WP2FA;
use WP2FA\Methods\Backup_Codes;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Methods\TOTP;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
if ( ! class_exists( '\WP2FA\Admin\Views\First_Time_Wizard_Steps' ) ) {
/**
* WP2FA First Wizard Settings view controller
*
* @since 1.7
*/
class First_Time_Wizard_Steps {
/**
* Select method step
*
* @since 1.7.0
*
* @param boolean $setup_wizard - Boolean - is that first time wizard setup or settings page call.
*
* @return void
*/
public static function select_method( $setup_wizard = false ) {
ob_start();
?>
<h3><?php \esc_html_e( 'Which 2FA methods can your users use?', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'When you uncheck any of the below 2FA methods it won\'t be available for your users to use. You can always change this later on from the plugin\'s settings.', 'wp-2fa' ); ?>
</p>
<?php
$data_role = 'data-role="global"';
if ( ! $setup_wizard ) {
?>
<table class="form-table">
<tbody>
<tr>
<th colspan="2"><?php \esc_html_e( 'Which of the below 2FA methods can users use?', 'wp-2fa' ); ?></th>
</tr>
<tr>
<th><label for="2fa-method"><?php \esc_html_e( 'Select the methods', 'wp-2fa' ); ?></label></th>
<td>
<?php } ?>
<fieldset id="2fa-method-select" class="wp-2fa-method-select">
<p class="method-title" style="padding-bottom: 20px;"><em><?php \esc_html_e( 'Primary 2FA methods:', 'wp-2fa' ); ?></em></p>
<?php
/**
* Fired right after the TOTP method HTML rendering.
*
* @param bool $wizard - Is that a wizard call or settings call.
* @param string $data_role - String with the JS data to add to form element.
* @param string $name - The name of the role.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'methods_setup', $setup_wizard, $data_role, null );
?>
<br />
<?php
if ( ! $setup_wizard ) {
$class = '';
if ( '' === trim( (string) Settings::get_role_or_default_setting( TOTP::POLICY_SETTINGS_NAME, null, null, true ) ) && '' === trim( (string) Settings::get_role_or_default_setting( 'enable_email', null, null, true ) ) && '' === trim( (string) Settings::get_role_or_default_setting( 'enable_oob_email', null, null, true ) ) ) {
$class = 'disabled';
}
?>
<div class="method-title"><em><?php \esc_html_e( 'Secondary 2FA methods:', 'wp-2fa' ); ?></em></div>
<br>
<label for="backup-codes" class=" <?php echo $class; // phpcs:ignore ?>">
<input type="checkbox" class="<?php echo \esc_attr( $class ); ?>" id="backup-codes" name="wp_2fa_policy[backup_codes_enabled]"
<?php echo $data_role; // phpcs:ignore ?>
value="yes"
<?php checked( WP2FA::get_wp2fa_setting( Backup_Codes::get_settings_name() ), Backup_Codes::get_settings_default_value() ); ?>
>
<?php
\esc_html_e( 'Backup codes', 'wp-2fa' );
if ( $setup_wizard ) {
echo '<p class="description">Note: ';
} else {
echo ' - ';
}
\esc_html_e( 'Backup codes are a secondary method which you can use to log in to the website in case the primary 2FA method is unavailable. Therefore they can\'t be enabled and used as a primary method.', 'wp-2fa' );
if ( $setup_wizard ) {
echo '</p>';
}
?>
</label>
<?php
/**
* Fires after the backup methods HTML rendering is finished.
*
* @param bool $wizard - Is that wizard ot standard setting.
* @param string $data_role - The JS data attribute for the form inputs.
* @param string $role - The name of the user role.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'after_backup_methods_setup', $setup_wizard, $data_role, null );
}
?>
</fieldset>
<?php
if ( ! $setup_wizard ) {
?>
</td>
</tr>
</tbody>
</table>
<?php } ?>
<?php
$output = ob_get_clean();
/**
* At this point, none of the default providers is set / activated. This filter allows additional providers to change the behavior. Checking the input array for specific values (methods), and based on that we can raise error that none of the allowed methods has bees selected by the user, or dismiss the error otherwise.
*
* @param string $output - Parsed HTML with the methods.
* @param bool $setup_wizard - The type of the wizard (first time wizard / settings).
*
* @since 2.0.0
*/
$output = apply_filters( WP_2FA_PREFIX . 'select_methods', $output, $setup_wizard );
echo $output; // phpcs:ignore
}
/**
* Builds the backup methods html
*
* @param boolean $setup_wizard - Is that call from the Wizard or not.
*
* @return void
*
* @since 2.4.1
*/
public static function backup_method( $setup_wizard = false ) {
ob_start();
?>
<h3><?php \esc_html_e( 'Which alternative 2FA methods can users use?', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'An alternative 2FA method allows users to configure another 2FA method that can be used as a backup should the primary 2FA method fail. This can happen if, for example, a user forgets their smartphone, the smartphone runs out of battery, or there are email deliverability problems.', 'wp-2fa' ); ?>
</p>
<p class="description">
<?php \esc_html_e( 'It is highly recommended to have an alternative 2FA method configured at all times. Below is a list of alternative 2FA methods available through this plugin:', 'wp-2fa' ); ?>
</p>
<br>
<fieldset>
<label for="backup-codes">
<input type="checkbox" id="backup-codes-global" name="wp_2fa_policy[backup_codes_enabled]" value="yes"
<?php checked( WP2FA::get_wp2fa_setting( Backup_Codes::get_settings_name() ), Backup_Codes::get_settings_default_value() ); ?>
>
<?php \esc_html_e( 'Backup codes', 'wp-2fa' ); ?>
</label>
<?php
echo '<p class="description">';
printf( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'%1$1s <a href="https://melapress.com/support/kb/wp-2fa-what-are-2fa-backup-codes/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">%2$1s</a> <br><br>',
\esc_html__( 'Backup codes allow users to log in to WordPress should they find themselves unable to log in via the primary 2FA method. Backup codes are enabled by default and are generated during the 2FA configuration process. Each backup code can be used only once. Once the initial list is exhausted, more backup codes can be generated through the user’s WordPress profile page - ', 'wp-2fa' ),
\esc_html__( 'More information', 'wp-2fa' )
);
echo '</p>';
?>
<?php
/* @free:start */
echo '<label>';
printf( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'%1$1s <a href="https://melapress.com/wordpress-2fa/features/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">%2$1s</a> %3$1s',
\esc_html__( 'Upgrade to WP 2FA Premium for', 'wp-2fa' ),
\esc_html__( 'more alternative 2FA methods', 'wp-2fa' ),
\esc_html__( 'to give your users more options.', 'wp-2fa' )
);
echo '<label>';
/* @free:end */
?>
</fieldset>
<?php
?>
<?php
$output = ob_get_clean();
$output = apply_filters( WP_2FA_PREFIX . 'backup_methods', $output, $setup_wizard );
echo $output; // phpcs:ignore
}
/**
* Enforcement policy step
*
* @since 1.7.0
*
* @param boolean $setup_wizard - Boolean - is that first time wizard setup or settings page call.
*
* @return void
*/
public static function enforcement_policy( $setup_wizard = false ) {
?>
<h3 id="enforcement_settings"><?php \esc_html_e( 'Do you want to enforce 2FA for some, or all the users? ', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'When you enforce 2FA the users will be prompted to configure 2FA the next time they login. Users have a grace period for configuring 2FA. You can configure the grace period and also exclude user(s) or role(s) in this settings page. ', 'wp-2fa' ); ?> <a href="https://melapress.com/support/kb/wp-2fa-configure-2fa-policies-enforce/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank" rel=noopener><?php \esc_html_e( 'Learn more.', 'wp-2fa' ); ?></a>
</p>
<?php
if ( ! $setup_wizard ) {
?>
<table class="form-table js-enforcement-policy-section">
<tbody>
<tr>
<th><label for="enforcement-policy"><?php \esc_html_e( 'Enforce 2FA on', 'wp-2fa' ); ?></label></th>
<td>
<?php } ?>
<fieldset class="contains-hidden-inputs">
<label for="all-users" style="margin:.35em 0 .5em !important; display: block;">
<input type="radio" name="wp_2fa_policy[enforcement-policy]" id="all-users" value="all-users"
<?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'all-users' ); ?>
>
<span><?php \esc_html_e( 'All users', 'wp-2fa' ); ?></span>
</label>
<?php if ( WP_Helper::is_multisite() ) : ?>
<label for="superadmins-only" style="margin:.35em 0 .5em !important; display: block;">
<input type="radio" name="wp_2fa_policy[enforcement-policy]" id="superadmins-only" value="superadmins-only"
<?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'superadmins-only' ); ?> />
<span><?php \esc_html_e( 'Only super admins', 'wp-2fa' ); ?></span>
</label>
<label for="superadmins-siteadmins-only" style="margin:.35em 0 .5em !important; display: block;">
<input type="radio" name="wp_2fa_policy[enforcement-policy]" id="superadmins-siteadmins-only" value="superadmins-siteadmins-only"
<?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'superadmins-siteadmins-only' ); ?> />
<span><?php \esc_html_e( 'Only super admins and site admins', 'wp-2fa' ); ?></span>
</label>
<?php endif; ?>
<label for="certain-roles-only" style="margin:.35em 0 .5em !important; display: block;">
<?php $checked = in_array( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), array( 'certain-roles-only', 'certain-users-only' ), true ); ?>
<input type="radio" name="wp_2fa_policy[enforcement-policy]" id="certain-roles-only" value="certain-roles-only"
<?php ( $setup_wizard ) ? checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'certain-roles-only' ) : checked( $checked ); ?>
data-unhide-when-checked=".certain-roles-only-inputs, .certain-users-only-inputs">
<span><?php \esc_html_e( 'Only for specific users and roles', 'wp-2fa' ); ?></span>
</label>
<fieldset class="hidden certain-users-only-inputs">
<div>
<p>
<label for="enforced_users-multi-select"><?php \esc_html_e( 'Users :', 'wp-2fa' ); ?></label> <select multiple="multiple" id="enforced_users-multi-select" name="wp_2fa_policy[enforced_users][]" style=" display:none;width:<?php echo ( $setup_wizard ) ? '100' : '50'; ?>%">
<?php
$enforced_users = (array) WP2FA::get_wp2fa_setting( 'enforced_users' );
foreach ( $enforced_users as $user ) {
?>
<option selected="selected" value="<?php echo \esc_attr( $user ); ?>"><?php echo \esc_attr( $user ); ?></option>
<?php
}
?>
</select>
</p>
</div>
</fieldset>
<fieldset class="hidden certain-roles-only-inputs">
<div>
<p style="margin-top: 0;">
<label for="enforced-roles-multi-select"><?php \esc_html_e( 'Roles :', 'wp-2fa' ); ?></label>
<select multiple="multiple" id="enforced-roles-multi-select" name="wp_2fa_policy[enforced_roles][]" style=" display:none;width:<?php echo ( $setup_wizard ) ? '100' : '50'; ?>%">
<?php
$all_roles = WP_Helper::get_roles_wp();
$enforced_roles = (array) WP2FA::get_wp2fa_setting( 'enforced_roles' );
foreach ( $all_roles as $role => $role_name ) {
$selected = '';
if ( in_array( $role, $enforced_roles, true ) ) {
$selected = 'selected="selected"';
}
?>
<option <?php echo $selected; // phpcs:ignore ?> value="<?php echo \esc_attr( strtolower( $role ) ); ?>"><?php echo \esc_html( $role_name ); ?></option>
<?php
}
?>
</select>
</p>
</div>
<?php if ( WP_Helper::is_multisite() ) { ?>
<p class="description">
<input type="checkbox" name="wp_2fa_policy[superadmins-role-add]" id="superadmins-role-add" value="yes" style="position: relative; top: -3px;"
<?php checked( WP2FA::get_wp2fa_setting( 'superadmins-role-add' ), 'yes' ); ?> />
<label for="superadmins-role-add"><?php \esc_html_e( 'Also enforce 2FA on network users with super admin privileges', 'wp-2fa' ); ?></label>
</p>
<?php } ?>
</fieldset>
<?php if ( WP_Helper::is_multisite() ) { ?>
<div>
<label for="enforce-on-multisite" style="margin:.35em 0 .5em !important; display: block;">
<input type="radio" name="wp_2fa_policy[enforcement-policy]" id="enforce-on-multisite" value="enforce-on-multisite"
<?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'enforce-on-multisite' ); ?>
data-unhide-when-checked=".all-sites">
<span><?php \esc_html_e( 'These sub-sites', 'wp-2fa' ); ?></span>
</label>
<fieldset class="hidden all-sites">
<p>
<label for="enforced-sites-multi-select"><?php \esc_html_e( 'Sites :', 'wp-2fa' ); ?></label> <select multiple="multiple" id="enforced-sites-multi-select" name="wp_2fa_policy[included_sites][]" style="display:none; width:<?php echo ( $setup_wizard ) ? '100' : '50'; ?>%">
<?php
$selected_sites = (array) WP2FA::get_wp2fa_setting( 'included_sites' );
foreach ( WP_Helper::get_multi_sites() as $site ) {
$args = array(
'blog_id' => $site->blog_id,
);
$current_blog_details = get_blog_details( $args );
$selected = '';
if ( in_array( $site->blog_id, $selected_sites, true ) ) {
$selected = 'selected="selected"';
}
?>
<option <?php echo $selected; // phpcs:ignore ?> value="<?php echo \esc_attr( $site->blog_id ); ?>"><?php echo \esc_html( $current_blog_details->blogname ); ?></option>
<?php
}
?>
</select>
</p>
</fieldset>
</div>
<?php } ?>
<div>
<label for="do-not-enforce" style="margin:.35em 0 .5em !important; display: block;">
<input type="radio" name="wp_2fa_policy[enforcement-policy]" id="do-not-enforce" value="do-not-enforce"
<?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'do-not-enforce' ); ?>
>
<span><?php \esc_html_e( 'Do not enforce on any users', 'wp-2fa' ); ?></span>
</label>
</div>
<br/>
</fieldset>
<?php
if ( ! $setup_wizard ) {
?>
</td>
</tr>
</tbody>
</table>
<?php
}
}
/**
* Exclude users and groups
*
* @since 1.7.0
*
* @param boolean $setup_wizard - Boolean - is that first time wizard setup or settings page call.
*
* @return void
*/
public static function exclude_users( $setup_wizard = false ) {
?>
<h3><?php \esc_html_e( 'Do you want to exclude any users or roles from 2FA? ', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'If you are enforcing 2FA on all users but for some reason you would like to exclude individual user(s) or users with a specific role, you can exclude them below', 'wp-2fa' ); ?>
</p>
<?php
if ( ! $setup_wizard ) {
?>
<table class="form-table js-enforcement-policy-section">
<tbody>
<tr>
<th><label id="exclude-users" for="excluded-users-multi-select"><?php \esc_html_e( 'Exclude the following users', 'wp-2fa' ); ?></label></th>
<td>
<?php } else { ?>
<label for="excluded-users-multi-select"><?php \esc_html_e( 'Exclude the following users', 'wp-2fa' ); ?>
<?php } ?>
<fieldset>
<div>
<select multiple="multiple" id="excluded-users-multi-select" name="wp_2fa_policy[excluded_users][]" style=" display:none;width:<?php echo ( $setup_wizard ) ? '100' : '50'; ?>%">
<?php
$excluded_users = (array) WP2FA::get_wp2fa_setting( 'excluded_users' );
foreach ( $excluded_users as $user ) {
?>
<option selected="selected" value="<?php echo \esc_attr( $user ); ?>"><?php echo \esc_html( $user ); ?></option>
<?php
}
?>
</select>
</div>
<?php
if ( ! $setup_wizard ) {
?>
</td>
</tr>
<tr>
<th><label for="excluded-roles-multi-select"><?php \esc_html_e( 'Exclude the following roles', 'wp-2fa' ); ?></label></th>
<td>
<p>
<?php } else { ?>
<br>
<label for="excluded-roles-multi-select"><?php \esc_html_e( 'Exclude the following roles', 'wp-2fa' ); ?></label>
<?php } ?>
<select multiple="multiple" id="excluded-roles-multi-select" name="wp_2fa_policy[excluded_roles][]" style=" display:none;width:<?php echo ( $setup_wizard ) ? '100' : '50'; ?>%">
<?php
$all_roles = WP_Helper::get_roles_wp();
$excluded_roles = (array) WP2FA::get_wp2fa_setting( 'excluded_roles' );
foreach ( $all_roles as $role => $role_name ) {
$selected = '';
if ( in_array( strtolower( $role ), $excluded_roles, true ) ) {
$selected = 'selected="selected"';
}
?>
<option <?php echo $selected; // phpcs:ignore ?> value="<?php echo \esc_attr( strtolower( $role ) ); ?>"><?php echo \esc_html( $role_name ); ?></option>
<?php
}
?>
</select>
<br>
<?php if ( WP_Helper::is_multisite() ) { ?>
<div style="margin-top:10px;">
<input type="checkbox" name="wp_2fa_policy[superadmins-role-exclude]" id="superadmins-role-exclude" value="yes"
<?php checked( WP2FA::get_wp2fa_setting( 'superadmins-role-exclude' ), 'yes' ); ?> />
<label for="superadmins-role-exclude"><?php \esc_html_e( 'Also exclude users with super admin privilege', 'wp-2fa' ); ?></label>
</div>
<?php } ?>
</fieldset>
<?php
if ( ! $setup_wizard ) {
?>
</td>
</tr>
</tbody>
</table>
<?php } ?>
<?php
}
/**
* Which network sites to exclude (for multisite instal)
*
* @since 1.7.0
*
* @param boolean $setup_wizard - Boolean - is that first time wizard setup or settings page call.
*
* @return void
*/
public static function excluded_network_sites( $setup_wizard = false ) {
?>
<h3><?php \esc_html_e( 'Do you want to exclude all the users of a site from 2FA? ', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'If you are enforcing 2FA on all users but for some reason you do not want to enforce it on a specific sub site, specify the sub site name below:', 'wp-2fa' ); ?>
</p>
<?php
if ( ! $setup_wizard ) {
?>
<table class="form-table js-enforcement-policy-section">
<tbody>
<tr>
<th><label for="excluded-sites-multi-select"><?php \esc_html_e( 'Exclude the following sites', 'wp-2fa' ); ?></label></th>
<td>
<?php } ?>
<fieldset>
<?php
if ( $setup_wizard ) {
?>
<div class="option-pill">
<label for="excluded_sites_search"><?php \esc_html_e( 'Exclude the following sites', 'wp-2fa' ); ?>
<?php } ?>
<select multiple="multiple" id="excluded-sites-multi-select" name="wp_2fa_policy[excluded_sites][]" style=" display:none;width:<?php echo ( $setup_wizard ) ? '100' : '50'; ?>%">
<?php
$excluded_sites = (array) WP2FA::get_wp2fa_setting( 'excluded_sites' );
if ( ! empty( $excluded_sites ) ) {
foreach ( $excluded_sites as $site_id ) {
$site = get_blog_details( $site_id )->blogname;
?>
<option selected="selected" value="<?php echo \esc_attr( $site_id ); ?>"><?php echo \esc_html( $site ); ?></option>
<?php
}
}
?>
</select>
<?php
if ( $setup_wizard ) {
?>
</label>
</div>
<?php } ?>
</fieldset>
<?php
if ( ! $setup_wizard ) {
?>
</td>
</tr>
</tbody>
</table>
<?php } ?>
<?php
}
/**
* Set the grace period
*
* @since 1.7.0
*
* @param boolean $setup_wizard - Boolean - is that first time wizard setup or settings page call.
*
* @return void
*/
public static function grace_period( $setup_wizard = false ) {
$grace_period = (int) WP2FA::get_wp2fa_setting( 'grace-period', true );
/**
* Via that, you can change the grace period TTL.
*
* @param bool - Default at this point is true - no method is selected.
*/
$testing = apply_filters( WP_2FA_PREFIX . 'allow_grace_period_in_seconds', false );
if ( $testing ) {
$grace_max = 600;
} else {
$grace_max = 10;
}
?>
<fieldset class="contains-hidden-inputs">
<label for="no-grace-period" style="margin-bottom: 10px; display: block;">
<input type="radio" name="wp_2fa_policy[grace-policy]" id="no-grace-period" value="no-grace-period"
<?php checked( WP2FA::get_wp2fa_setting( 'grace-policy' ), 'no-grace-period' ); ?>
>
<span><?php \esc_html_e( 'Users have to configure 2FA straight away.', 'wp-2fa' ); ?></span>
</label>
<label for="use-grace-period">
<input type="radio" name="wp_2fa_policy[grace-policy]" id="use-grace-period" value="use-grace-period"
<?php checked( WP2FA::get_wp2fa_setting( 'grace-policy' ), 'use-grace-period' ); ?>
data-unhide-when-checked=".grace-period-inputs">
<span><?php \esc_html_e( 'Give users a grace period to configure 2FA', 'wp-2fa' ); ?></span>
</label>
<fieldset class="hidden grace-period-inputs">
<br/>
<input type="number" id="grace-period" name="wp_2fa_policy[grace-period]" value="<?php echo \esc_attr( $grace_period ); ?>" min="1" max="<?php echo \esc_attr( $grace_max ); ?>">
<label class="radio-inline">
<input class="js-nested" type="radio" name="wp_2fa_policy[grace-period-denominator]" value="hours"
<?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'hours' ); ?>
>
<?php \esc_html_e( 'hours', 'wp-2fa' ); ?>
</label>
<label class="radio-inline">
<input class="js-nested" type="radio" name="wp_2fa_policy[grace-period-denominator]" value="days"
<?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'days' ); ?>
>
<?php \esc_html_e( 'days', 'wp-2fa' ); ?>
</label>
<?php
/**
* Fires after the grace period. Gives the ability to change the parsed code.
*
* @param string $content - HTML content.
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @since 2.0.0
*/
$after_grace_content = \apply_filters( WP_2FA_PREFIX . 'after_grace_period', '', '', 'wp_2fa_policy' );
echo $after_grace_content; // phpcs:ignore
?>
<?php
/**
* Via that, you can change the grace period TTL.
*
* @param bool - Default at this point is true - no method is selected.
*/
$testing = apply_filters( WP_2FA_PREFIX . 'allow_grace_period_in_seconds', false );
if ( $testing ) {
?>
<label class="radio-inline">
<input class="js-nested" type="radio" name="wp_2fa_policy[grace-period-denominator]" value="seconds"
<?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'seconds' ); ?>
>
<?php \esc_html_e( 'Seconds', 'wp-2fa' ); ?>
</label>
<?php
}
if ( $setup_wizard ) {
$user = wp_get_current_user();
$last_user_to_update_settings = $user->ID;
?>
<input type="hidden" id="2fa_main_user" name="wp_2fa_policy[2fa_settings_last_updated_by]" value="<?php echo \esc_attr( $last_user_to_update_settings ); ?>">
<?php } else { ?>
<p><?php \esc_html_e( 'Note: If users do not configure it within the configured stipulated time, their account will be locked and have to be unlocked manually.', 'wp-2fa' ); ?></p>
<?php } ?>
</fieldset>
<br/>
</fieldset>
<?php
}
}
}
includes/classes/Admin/Views/class-passord-reset-2fa.php 0000644 00000012411 15075513060 0017212 0 ustar 00 <?php
/**
* Roles and main settings password reset class.
*
* @package wp2fa
* @subpackage views
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin\Views;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
if ( ! class_exists( '\WP2FA\Admin\Views\Password_Reset_2FA' ) ) {
/**
* Password_Reset_2FA - Class for rendering the plugin settings related to 2fa when user resets the password.
*
* @since 2.5.0
*/
class Password_Reset_2FA {
public const PASSWORD_RESET_SETTINGS_NAME = 'password-reset-2fa-show';
public const ENABLED_SETTING_VALUE = 'password-reset-2fa';
/**
* Inits all the class related hooks.
*
* @return void
*
* @since 2.5.0
*/
public static function init() {
if ( is_admin() ) {
\add_filter( WP_2FA_PREFIX . 'before_grace_period', array( __CLASS__, 'password_reset_setting' ), 10, 5 );
\add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'add_setting_value' ) );
}
\add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
}
/**
* Shows the settings for the grace period notifications behavior.
*
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @return string
*
* @since 2.5.0
*/
public static function reset_settings( string $role = '', string $name_prefix = '', string $data_role = '', string $role_id = '' ) {
ob_start();
if ( class_exists( 'WP2FA\Extensions\RoleSettings\Role_Settings_Controller' ) ) {
$password_reset_action = Role_Settings_Controller::get_setting( $role, self::PASSWORD_RESET_SETTINGS_NAME, true );
} else {
$password_reset_action = Settings::get_role_or_default_setting( self::PASSWORD_RESET_SETTINGS_NAME, null, null, true );
}
?>
<div class="sub-setting-indent">
<fieldset>
<label for="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?><?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 10px; display: inline-block;">
<input type="checkbox" name="<?php echo \esc_attr( $name_prefix ); ?>[<?php echo \esc_attr( self::PASSWORD_RESET_SETTINGS_NAME ); ?>]"
id="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?><?php echo \esc_attr( $role_id ); ?>"
<?php echo $data_role; // phpcs:ignore?>
value="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?>" <?php checked( $password_reset_action, self::ENABLED_SETTING_VALUE ); ?> class="js-nested">
<span><?php echo \esc_html__( 'Require 2FA on password reset', 'wp-2fa' ); ?></span>
</label>
</fieldset>
</div>
<?php
$html_content = ob_get_contents();
ob_end_clean();
return $html_content;
}
/**
* Adds global plugin setting options.
*
* @param array $loop_settings - Array with current plugin settings.
*
* @return array
*
* @since 2.5.0
*/
public static function add_setting_value( array $loop_settings ) {
$loop_settings[] = self::PASSWORD_RESET_SETTINGS_NAME;
return $loop_settings;
}
/**
* Adds the extension default settings to the main plugin settings.
*
* @param array $default_settings - array with plugin default settings.
*
* @return array
*
* @since 2.5.0
*/
public static function add_default_settings( array $default_settings ) {
$default_settings[ self::PASSWORD_RESET_SETTINGS_NAME ] = self::PASSWORD_RESET_SETTINGS_NAME;
return $default_settings;
}
/**
* Password reset settings.
*
* @param string $content - HTML content.
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @return string
*
* @since 2.5.0
*/
public static function password_reset_setting( string $content, string $role = '', string $name_prefix = '', string $data_role = '', string $role_id = '' ) {
ob_start();
?>
<h3><?php \esc_html_e( 'Do you want to require 2FA when users reset their password?', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'When you enable this setting users will be required to enter a one-time code sent to them via email when resetting the password.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?><?php echo \esc_attr( $role_id ); ?>"><?php \esc_html_e( 'Password reset', 'wp-2fa' ); ?></label></th>
<td>
<fieldset class="contains-hidden-inputs">
<?php echo self::reset_settings( $role, $name_prefix, $data_role, $role_id ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
$content .= ob_get_contents();
ob_end_clean();
return $content;
}
}
}
includes/classes/Admin/Views/class-grace-period-notifications.php 0000644 00000012233 15075513060 0021163 0 ustar 00 <?php
/**
* Roles and main settings grace period notifications class.
*
* @package wp2fa
* @subpackage views
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin\Views;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
if ( ! class_exists( '\WP2FA\Admin\Views\Grace_Period_Notifications' ) ) {
/**
* Grace_Period_Notifications - Class for rendering the grace period notification settings.
*
* @since 2.5.0
*/
class Grace_Period_Notifications {
public const GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME = 'grace-policy-notification-show';
/**
* Inits all the class related hooks.
*
* @return void
*
* @since 2.5.0
*/
public static function init() {
if ( is_admin() ) {
\add_filter( WP_2FA_PREFIX . 'after_grace_period', array( __CLASS__, 'grace_period_notification_settings' ), 11, 5 );
\add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'add_setting_value' ) );
}
\add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
}
/**
* Shows the settings for the grace period notifications behavior.
*
* @param string $content - HTML content.
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @return string
*
* @since 2.5.0
*/
public static function grace_period_notification_settings( string $content, string $role = '', string $name_prefix = '', string $data_role = '', string $role_id = '' ) {
ob_start();
if ( class_exists( 'WP2FA\Extensions\RoleSettings\Role_Settings_Controller' ) ) {
$expire_action = Role_Settings_Controller::get_setting( $role, self::GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME, true );
} else {
$expire_action = Settings::get_role_or_default_setting( self::GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME, null, null, true );
}
?>
<div class="sub-setting-indent">
<p class="description" style="margin-top: 15px; margin-bottom: 8px;">
<?php echo \esc_html__( 'How do you want users to be informed they are enforced to setup 2FA?', 'wp-2fa' ); ?>
</p>
<fieldset>
<label for="dashboard-notification<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 10px; display: inline-block;">
<input type="radio" name="<?php echo \esc_attr( $name_prefix ); ?>[<?php echo \esc_attr( self::GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME ); ?>]"
id="dashboard-notification<?php echo \esc_attr( $role_id ); ?>"
<?php echo $data_role; // phpcs:ignore?>
value="dashboard-notification" <?php checked( $expire_action, 'dashboard-notification' ); ?> class="js-nested">
<span><?php echo \esc_html__( 'Show an admin notice in the dashboard', 'wp-2fa' ); ?></span>
</label>
<br>
<div style="clear:both">
<label for="after-login-notification<?php echo \esc_attr( $role_id ); ?>">
<input type="radio" name="<?php echo \esc_attr( $name_prefix ); ?>[<?php echo \esc_attr( self::GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME ); ?>]" <?php checked( $expire_action, 'after-login-notification' ); ?>
id="after-login-notification<?php echo \esc_attr( $role_id ); ?>"
<?php echo $data_role; // phpcs:ignore?>
value="after-login-notification" <?php checked( $expire_action, 'after-login-notification' ); ?> class="js-nested">
<span><?php echo \esc_html__( 'Show a notification on a page on its own after the user authenticates and before accessing the dashboard', 'wp-2fa' ); ?></span>
</label>
</div>
</fieldset>
</div>
<?php
$html_content = ob_get_contents();
ob_end_clean();
return $content . $html_content;
}
/**
* Adds global plugin setting options.
*
* @param array $loop_settings - Array with current plugin settings.
*
* @return array
*
* @since 2.5.0
*/
public static function add_setting_value( array $loop_settings ) {
$loop_settings[] = self::GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME;
return $loop_settings;
}
/**
* Checks the grace policy setting for the given user.
*
* @param \WP_User $user - The user for which we have to check the settings.
*
* @return bool
*
* @since 2.5.0
*/
public static function notify_using_dashboard( \WP_User $user ) {
if ( 'dashboard-notification' !== Settings::get_role_or_default_setting( self::GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME, $user ) ) {
return false;
}
return true;
}
/**
* Adds the extension default settings to the main plugin settings
*
* @param array $default_settings - array with plugin default settings.
*
* @return array
*
* @since 2.5.0
*/
public static function add_default_settings( array $default_settings ) {
$default_settings[ self::GRACE_PERIOD_NOTIFICATION_SETTINGS_NAME ] = 'after-login-notification';
return $default_settings;
}
}
}
includes/classes/Admin/Views/class-re-login-2fa.php 0000644 00000013113 15075513060 0016133 0 ustar 00 <?php
/**
* Roles and main settings user login again after 2FA setup class.
*
* @package wp2fa
* @subpackage views
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin\Views;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
if ( ! class_exists( '\WP2FA\Admin\Views\Re_Login_2FA' ) ) {
/**
* Re_Login_2FA - Class for rendering the plugin settings related to 2fa when user sets the 2FA method.
*
* @since 2.7.0
*/
class Re_Login_2FA {
public const RE_LOGIN_SETTINGS_NAME = 're-login-2fa-show';
public const ENABLED_SETTING_VALUE = 're-login-2fa';
/**
* Inits all the class related hooks.
*
* @return void
*
* @since 2.7.0
*/
public static function init() {
if ( is_admin() ) {
\add_filter( WP_2FA_PREFIX . 'before_grace_period', array( __CLASS__, 're_login_setting' ), 11, 5 );
\add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'add_setting_value' ) );
\add_action( 'wp_ajax_custom_ajax_logout', array( __CLASS__, 'redirect_after_logout' ) );
}
\add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
}
/**
* Logs out the current user and sends success message to the ajax request.
*
* @return void
*
* @since 2.7.0
*/
public static function redirect_after_logout() {
\wp_logout();
ob_clean(); // probably overkill for this, but good habit.
\wp_send_json_success();
}
/**
* Shows the settings for the grace period notifications behavior.
*
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @return string
*
* @since 2.7.0
*/
public static function reset_settings( string $role = '', string $name_prefix = '', string $data_role = '', string $role_id = '' ) {
ob_start();
if ( class_exists( 'WP2FA\Extensions\RoleSettings\Role_Settings_Controller' ) ) {
$password_reset_action = Role_Settings_Controller::get_setting( $role, self::RE_LOGIN_SETTINGS_NAME, true );
} else {
$password_reset_action = Settings::get_role_or_default_setting( self::RE_LOGIN_SETTINGS_NAME, null, null, true );
}
?>
<div class="sub-setting-indent">
<fieldset>
<label for="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?><?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 10px; display: inline-block;">
<input type="checkbox" name="<?php echo \esc_attr( $name_prefix ); ?>[<?php echo \esc_attr( self::RE_LOGIN_SETTINGS_NAME ); ?>]"
id="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?><?php echo \esc_attr( $role_id ); ?>"
<?php echo $data_role; // phpcs:ignore?>
value="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?>" <?php checked( $password_reset_action, self::ENABLED_SETTING_VALUE ); ?> class="js-nested">
<span><?php echo \esc_html__( 'Log out user after 2FA setup', 'wp-2fa' ); ?></span>
</label>
</fieldset>
</div>
<?php
$html_content = ob_get_contents();
ob_end_clean();
return $html_content;
}
/**
* Adds global plugin setting options.
*
* @param array $loop_settings - Array with current plugin settings.
*
* @return array
*
* @since 2.7.0
*/
public static function add_setting_value( array $loop_settings ) {
$loop_settings[] = self::RE_LOGIN_SETTINGS_NAME;
return $loop_settings;
}
/**
* Adds the extension default settings to the main plugin settings.
*
* @param array $default_settings - array with plugin default settings.
*
* @return array
*
* @since 2.7.0
*/
public static function add_default_settings( array $default_settings ) {
$default_settings[ self::RE_LOGIN_SETTINGS_NAME ] = self::RE_LOGIN_SETTINGS_NAME;
return $default_settings;
}
/**
* Password reset settings.
*
* @param string $content - HTML content.
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @return string
*
* @since 2.7.0
*/
public static function re_login_setting( string $content, string $role = '', string $name_prefix = '', string $data_role = '', string $role_id = '' ) {
ob_start();
?>
<h3><?php \esc_html_e( 'Do you want to logout users after setting up 2FA on their account?', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'When you enable this setting users will be logged out automatically after configuring 2FA and they will need to log back in.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="<?php echo \esc_attr( self::ENABLED_SETTING_VALUE ); ?><?php echo \esc_attr( $role_id ); ?>"><?php \esc_html_e( 'Re-login', 'wp-2fa' ); ?></label></th>
<td>
<fieldset class="contains-hidden-inputs">
<?php echo self::reset_settings( $role, $name_prefix, $data_role, $role_id ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
$content .= ob_get_contents();
ob_end_clean();
return $content;
}
}
}
includes/classes/Admin/class-user-listing.php 0000644 00000016315 15075513060 0015310 0 ustar 00 <?php
/**
* Responsible for user listing in admin manipulation.
*
* @package wp2fa
* @subpackage user-utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin;
use WP2FA\Utils\User_Utils;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Extensions\TrustedDevices\Core;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* User_Listing class with user listing filters
*/
if ( ! class_exists( '\WP2FA\Admin\User_Listing' ) ) {
/**
* User_Listing - Shows extra column in user table wit WP2FA status forevery user
*/
class User_Listing {
/**
* The users table column name
*
* @var string
*/
private static $column_name = '2fa-status';
/**
* Inits all the hooks used for showing the extra user data in the users column
*
* @return void
*/
public static function init() {
\add_filter( 'manage_users_columns', array( __CLASS__, 'add_wp_2fa_column' ) );
\add_filter( 'wpmu_users_columns', array( __CLASS__, 'add_wp_2fa_column' ) );
\add_filter( 'manage_users_custom_column', array( __CLASS__, 'show_column_data' ), 10, 3 );
\add_filter( 'bulk_actions-users', array( __CLASS__, 'add_bulk_action' ), 10, 1 );
\add_filter( 'handle_bulk_actions-users', array( __CLASS__, 'handle_bulk_actions' ), 10, 3 );
\add_action( 'admin_notices', array( __CLASS__, 'show_admin_notice' ) );
\add_filter( 'user_row_actions', array( __CLASS__, 'add_users_hover' ), 10, 2 );
}
/**
* Sets the column in the admin users table
*
* @param array $columns - Array with all the columns.
*
* @return array
*/
public static function add_wp_2fa_column( array $columns ): array {
$columns[ self::$column_name ] = __( '2FA Status', 'wp-2fa' );
return $columns;
}
/**
* Shows the user WP 2FA status data in the users table
*
* @param [type] $value - The value of the column.
* @param string $column_name - The name of the column.
* @param int $user_id - the ID of the user.
*
* @return mixed
*/
public static function show_column_data( $value, string $column_name, $user_id ) {
switch ( $column_name ) {
case self::$column_name:
return self::get_user2fa_status( $user_id );
default:
}
return $value;
}
/**
* Retrieves the translated 2FA status label for given user.
*
* This is performance optimized version that bypasses the User class on purpose. It loads the 2FA status meta
* field directly and turns it into a label.
*
* There is also some temporary code to figure out the 2FA status meta field if it doesn't exist. This will be
* removed in future versions and exist purely so we don't end up with no values in the column after migration
* to version 1.7.0 when this was introduced.
*
* @param int $user_id - The id of the user for which the info should be extracted.
*
* @return string
*
* @since 1.7.0
*/
private static function get_user2fa_status( $user_id ) {
// try to get the user status "id" from user's meta data.
$status_meta_value = User_Helper::get_2fa_status( $user_id );
if ( ! empty( $status_meta_value ) ) {
// the status id is available, grab the label to display.
$status_data = User_Utils::extract_statuses( array( $status_meta_value ) );
if ( ! empty( $status_data ) ) {
return $status_data['label'];
}
}
// If the user status is not saved in user meta (this can be the case prior to version 1.7.0), we figure it
// out and store it against the user in DB. This is not ideal in terms of performance and this is only
// a temporary solution.
// @todo remove this in future versions.
return User_Helper::set_user_status( new \WP_User( $user_id ) );
}
/**
* Returns the users table column name
*
* @return string
*/
public static function get_column_name(): string {
return self::$column_name;
}
/**
* Adds bulk action to the WP users menu
*
* @param array $bulk_actions - Array of bulk actions.
*
* @return array
*
* @since 2.2.2
*/
public static function add_bulk_action( $bulk_actions ) {
$bulk_actions['remove-2fa'] = __( 'Remove 2FA', 'wp-2fa' );
$bulk_actions['remove-2fa-trusted'] = __( 'Reset list of 2FA trusted devices', 'wp-2fa' );
return $bulk_actions;
}
/**
* Removes the 2fa from the list of the selected users.
*
* @param string $redirect_url - The redirect URL to redirect to when action is performed.
* @param string $action - The action to perform.
* @param array $user_ids - The user IDs to remove from.
*
* @return string
*
* @since 2.2.2
*/
public static function handle_bulk_actions( $redirect_url, $action, $user_ids ) {
if ( 'remove-2fa' === $action ) {
if ( is_array( $user_ids ) ) {
foreach ( $user_ids as $user_id ) {
User_Helper::remove_2fa_for_user(
$user_id
);
}
$num_of_ids = count( $user_ids );
} else {
User_Helper::remove_2fa_for_user(
$user_ids
);
$num_of_ids = 1;
}
$redirect_url = add_query_arg( '2fa-removed', $num_of_ids, $redirect_url );
}
if ( class_exists( '\WP2FA\Extensions\TrustedDevices\Core' ) && 'remove-2fa-trusted' === $action ) {
if ( is_array( $user_ids ) ) {
Core::remove_trusted_devices_for_users(
$user_ids
);
$num_of_ids = count( $user_ids );
} else {
Core::remove_trusted_devices_for_users(
array( $user_ids )
);
$num_of_ids = 1;
}
$redirect_url = add_query_arg( '2fa-trusted-removed', $num_of_ids, $redirect_url );
}
return $redirect_url;
}
/**
* Adds links to the on hover state of the users table row
*
* @param array $actions - Array with all the actions for the current row.
* @param \WP_User $user_object - The user object from the current row.
*
* @return array
*
* @since 2.4.0
*/
public static function add_users_hover( $actions, $user_object ): array {
if ( class_exists( '\WP2FA\Extensions\TrustedDevices\Core' ) ) {
$actions['remove-2fa-trusted'] = "<a class='resetpassword' href='" . \wp_nonce_url( "users.php?action=remove-2fa-trusted&users=$user_object->ID", 'bulk-users' ) . "'>" . __( 'Reset list of 2FA trusted devices', 'wp-2fa' ) . '</a>';
}
return $actions;
}
/**
* Handles the Admin notice for the users removed 2FA.
*
* @return void
*
* @since 2.2.2
*/
public static function show_admin_notice() {
if ( ! empty( $_REQUEST['2fa-removed'] ) ) {
$num_changed = (int) $_REQUEST['2fa-removed'];
printf(
'<div id="message" class="updated notice is-dismissable"><p>' .
// translators: The number of the affected users.
\esc_html__( 'Removed 2FA from %d users.', 'wp-2fa' ) .
'</p></div>',
(int) $num_changed
);
}
if ( ! empty( $_REQUEST['2fa-trusted-removed'] ) ) {
$num_changed = (int) $_REQUEST['2fa-trusted-removed'];
printf(
'<div id="message" class="updated notice is-dismissable"><p>' .
// translators: The number of the affected users.
\esc_html__( 'Removed 2FA trusted devices from %d users.', 'wp-2fa' ) .
'</p></div>',
(int) $num_changed
);
}
}
}
}
includes/classes/Admin/Helpers/class-classes-helper.php 0000644 00000011605 15075513060 0017174 0 ustar 00 <?php
/**
* Responsible for the User's operations.
*
* @package wp2fa
* @subpackage helpers
*
* @since 2.4.0
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin\Helpers;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( '\WP2FA\Admin\Helpers\Classes_Helper' ) ) {
/**
* Responsible for the proper class loading.
*/
class Classes_Helper {
/**
* Holds the classmap array for more info check @see autoload_classmap.php from the auto generated Composer file.
*
* @var array
*
* @since 2.4.0
*/
private static $class_map = array();
/**
* Caches and returns the classmap structure of the plugin.
*
* @since 2.4.0
*/
public static function get_class_map(): array {
if ( empty( self::$class_map ) ) {
self::$class_map = require WP_2FA_PATH . 'vendor/composer/autoload_classmap.php';
}
return self::$class_map;
}
/**
* Returns the class by its filename. Checks if it exists and returns it as string. Returns false otherwise.
*
* @param string $file - The filename of the class to check.
*
* @return string|false
*
* @since 2.4.0
*/
public static function get_class_by_filename( string $file ) {
if ( in_array( $file, self::get_class_map(), true ) ) {
$class = array_search( $file, self::get_class_map(), true );
if ( class_exists( $class ) ) {
return $class;
}
}
return false;
}
/**
* Extracts subclasses of the given class, optionally abstract classes could be included as well.
*
* @param string $current_class - The calling class.
* @param string $base_class - The class which subclasses should be extracted.
* @param bool $exclude_abstracts - Should we exclude abstract classes.
*
* @since 2.4.0
*/
public static function get_subclasses_of_class( string $current_class, string $base_class, bool $exclude_abstracts = true ): array {
$matching_classes = array();
foreach ( array_keys( self::get_class_map() ) as $class_name ) {
if ( $current_class !== $class_name && is_subclass_of( $class_name, $base_class ) ) {
if ( $exclude_abstracts && ( false !== strpos( $class_name, 'Abstract' ) ) ) {
continue;
}
$matching_classes[ $class_name ] = $class_name;
}
}
return $matching_classes;
}
/**
* Returns all the classes which are part of the given namespace.
*
* @param string $extract_namespace - The extract_namespace to search for.
*
* @return array
*
* @since 2.4.0
*/
public static function get_classes_by_namespace( string $extract_namespace ) {
if ( 0 === strpos( $extract_namespace, '\\' ) ) {
$extract_namespace = ltrim( $extract_namespace, '\\' );
}
$extract_namespace = rtrim( $extract_namespace, '\\' );
$term_upper = strtoupper( $extract_namespace );
return array_filter(
array_keys( self::get_class_map() ),
function ( $found_class ) use ( $term_upper ) {
$class_name = strtoupper( $found_class );
/**
* Find class name, by finding the last occurrence of the \
* if it is false (from the strrchr) then class does not belong to any namespace currently.
*/
$esc_position = strrchr( $class_name, '\\' );
if ( false !== $esc_position ) {
$class_name_no_ns = substr( $esc_position, 1 );
} else {
return false;
}
if ( $class_name_no_ns &&
$term_upper . '\\' . $class_name_no_ns === $class_name &&
false === strpos( $class_name, strtoupper( 'Abstract' ) ) &&
false === strpos( $class_name, strtoupper( 'Interface' ) )
) {
return $found_class;
}
return false;
}
);
}
/**
* Search for classes by given term.
*
* @param string $term - The term to search for.
*
* @return array
*
* @since 2.4.0
*/
public static function get_classes_with_term( $term ) {
$term_upper = strtoupper( $term );
return array_filter(
self::get_class_map(),
function ( $found_class ) use ( $term_upper ) {
$class_name = strtoupper( $found_class );
if (
false !== strpos( $class_name, $term_upper ) &&
false === strpos( $class_name, strtoupper( 'Abstract' ) ) &&
false === strpos( $class_name, strtoupper( 'Interface' ) )
) {
return $found_class;
}
return false;
}
);
}
/**
* Adds a class (or classes) to the class map.
*
* @param array $class_add - Array with class or classes to add.
*
* @return void
*
* @since 2.7.0
*/
public static function add_to_class_map( array $class_add ) {
if ( empty( self::$class_map ) ) {
self::$class_map = require WP_2FA_PATH . 'vendor/composer/autoload_classmap.php';
}
self::$class_map = \array_merge( self::$class_map, $class_add );
}
}
}
includes/classes/Admin/Helpers/class-file-writer.php 0000644 00000052650 15075513060 0016520 0 ustar 00 <?php
/**
* Responsible for the File writing operations
*
* @package wp2fa
* @subpackage helpers
* @since 2.4.0
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Helpers;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* File writer settings class
*/
if ( ! class_exists( '\WP2FA\Admin\Helpers\File_Writer' ) ) {
/**
* All the file operations must go trough this class.
*
* @since 2.4.0
*/
class File_Writer {
public const SECRET_NAME = 'WP2FA_ENCRYPT_KEY';
public const WP2FA_UPLOADS_DIR = 'wp-2fa-data';
/**
* Saves a secret key in `wp-config.php`.
*
* @param string $secret The secret key to save.
*
* @return bool
*
* @since 2.4.0
*/
public static function save_secret_key( string $secret ): bool {
if ( ! self::can_write_to_file( self::get_wp_config_file_path() ) ) {
return false;
}
$file = self::get_wp_config_file_path();
$contents = self::read( $file );
if ( false === $contents ) {
return false;
}
\set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
function ( $err_severity, $err_msg, $err_file, $err_line, array $err_context ) {
throw new \Error( $err_msg, 0, $err_severity, $err_file, $err_line ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
},
E_WARNING
);
try {
$current_secret = constant( self::SECRET_NAME );
} catch ( \Error $e ) {
$current_secret = null;
}
restore_error_handler();
$matches_found = $current_secret ? substr_count( $contents, $current_secret ) : 0;
if ( ! $current_secret || ! $matches_found ) {
if ( substr_count( $contents, self::SECRET_NAME ) ) {
$line_ending = self::get_line_ending( $contents );
$contents = explode( $line_ending, $contents );
foreach ( $contents as $key => $line ) {
if ( stristr( $line, self::SECRET_NAME ) ) {
unset( $contents[ $key ] );
}
}
$contents = implode( $line_ending, array_values( $contents ) );
self::write( $file, $contents );
}
self::write_wp_config( '/** WP 2FA plugin data encryption key. For more information please visit melapress.com */' . "\n" . 'define( \'' . self::SECRET_NAME . '\', \'' . $secret . '\' );' );
return true;
}
if ( $matches_found > 1 ) {
return false;
}
$replaced = str_replace( $current_secret, $secret, $contents );
if ( ! $replaced ) {
return false;
}
$written = self::write( $file, $replaced );
if ( false === $written ) {
return false;
}
return true;
}
/**
* Gets the permissions of given directory
*
* @param string $dir - The name of the directory to check.
*
* @return bool|int
*
* @since 2.4.0
*/
public static function get_permissions( string $dir ) {
if ( ! is_dir( $dir ) ) {
return false;
}
if ( ! PHP_Helper::is_callable( 'fileperms' ) ) {
return false;
}
$dir = rtrim( $dir, '/' );
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
@clearstatcache( true, $dir );
return fileperms( $dir ) & 0777;
}
/**
* Writes a content to a given file
*
* @param string $file - The file to write to.
* @param string $contents - The contents of the file to write.
* @param boolean $append - Append the contents of the file or overwrite.
*
* @return mixed
*
* @since 2.4.0
*/
public static function write( string $file, string $contents, $append = false ) {
$callable = array();
if ( PHP_Helper::is_callable( 'fopen' ) && PHP_Helper::is_callable( 'fwrite' ) && PHP_Helper::is_callable( 'flock' ) ) {
$callable[] = 'fopen';
}
if ( PHP_Helper::is_callable( 'file_put_contents' ) ) {
$callable[] = 'file_put_contents';
}
if ( empty( $callable ) ) {
return false;
}
if ( is_dir( $file ) ) {
return false;
}
if ( ! is_dir( dirname( $file ) ) ) {
$result = self::create_dir( dirname( $file ) );
if ( false === $result ) {
return false;
}
}
$file_existed = is_file( $file );
$success = false;
// Different permissions to try in case the starting set of permissions are prohibiting write.
$trial_perms = array(
false,
0644,
0664,
0666,
);
foreach ( $trial_perms as $perms ) {
if ( false !== $perms ) {
if ( ! isset( $original_file_perms ) ) {
$original_file_perms = self::get_permissions( $file );
}
self::chmod( $file, $perms );
}
if ( in_array( 'fopen', $callable, true ) ) {
if ( $append ) {
$mode = 'ab';
} else {
$mode = 'wb';
}
if ( false !== ( $fh = @fopen( $file, $mode ) ) ) { // phpcs:ignore -- Ignored the assignment on the same line
flock( $fh, LOCK_EX );
mbstring_binary_safe_encoding();
$data_length = strlen( $contents );
$bytes_written = @fwrite( $fh, $contents ); // phpcs:ignore -- Ignored the error silencing
reset_mbstring_encoding();
@flock( $fh, LOCK_UN ); // phpcs:ignore -- Ignored the error silencing
@fclose( $fh ); // phpcs:ignore -- Ignored the error silencing
if ( $data_length === $bytes_written ) {
$success = true;
}
}
}
if ( ! $success && in_array( 'file_put_contents', $callable, true ) ) {
if ( $append ) {
$flags = FILE_APPEND;
} else {
$flags = 0;
}
mbstring_binary_safe_encoding();
$data_length = strlen( $contents );
$bytes_written = @file_put_contents( $file, $contents, $flags ); // phpcs:ignore -- Ignored the silencing warning
reset_mbstring_encoding();
if ( $data_length === $bytes_written ) {
$success = true;
}
}
if ( $success ) {
if ( ! $file_existed ) {
// Set default file permissions for the new file.
self::chmod( $file, self::get_default_permissions() );
} elseif ( isset( $original_file_perms ) && ! is_wp_error( $original_file_perms ) ) {
// Reset the original file permissions if they were modified.
self::chmod( $file, $original_file_perms );
}
return true;
}
if ( ! $file_existed ) {
// If the file is new, there is no point attempting different permissions.
break;
}
}
return false;
}
/**
* Adds index.php and .htaccess files to the given directory
*
* @param string $dir - The directory to protect.
*
* @return bool
*
* @since 2.4.0
*/
public static function add_file_listing_protection( string $dir ) {
$dir = rtrim( $dir, \DIRECTORY_SEPARATOR );
if ( ! is_dir( $dir ) ) {
return false;
}
if ( self::exists( $dir . \DIRECTORY_SEPARATOR . 'index.php' ) ) {
return true;
}
return self::write( $dir . \DIRECTORY_SEPARATOR . '.htaccess', 'Deny from all' ) &&
self::write( $dir . \DIRECTORY_SEPARATOR . 'index.php', "<?php\n// Silence is golden." );
}
/**
* Checks if given file exists
*
* @param string $file - The name of the file to check.
*
* @return bool
*
* @since 2.4.0
*/
public static function exists( string $file ): bool {
@clearstatcache( true, $file ); // phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
return @file_exists( $file ); // phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
}
/**
* Check the setting that allows writing files.
*
* @param string $filename - The name of the file and path.
*
* @since 2.4.0
*
* @return bool True if files can be written to, false otherwise.
*/
public static function can_write_to_file( string $filename ) {
return is_writable( $filename );
}
/**
* Get full file path to the site's wp-config.php file.
*
* @since 2.4.0
*
* @return string Full path to the wp-config.php file or a blank string if modifications for the file are disabled.
*/
public static function get_wp_config_file_path() {
if ( file_exists( ABSPATH . 'wp-config.php' ) ) {
/** The config file resides in ABSPATH */
$path = ABSPATH . 'wp-config.php';
} elseif ( @file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
/** The config file resides one level above ABSPATH */
$path = dirname( ABSPATH ) . '/wp-config.php';
} else {
$path = '';
}
/**
* Gives the ability to manually change the path to the config file.
*
* @param string - The current value for WP config file path.
*
* @since 2.6.2
*/
$path = \apply_filters( WP_2FA_PREFIX . 'config_file_path', (string) $path );
return $path;
}
/**
* Creates a directory structure
*
* @param string $dir - The directory to create.
*
* @return boolean
*
* @since 2.4.0
*/
public static function create_dir( string $dir ): bool {
$dir = rtrim( $dir, '/' );
if ( is_dir( $dir ) ) {
self::add_file_listing_protection( $dir );
return true;
}
if ( self::exists( $dir ) ) {
return false;
}
if ( ! PHP_Helper::is_callable( 'mkdir' ) ) {
return false;
}
$parent = dirname( $dir );
while ( ! empty( $parent ) && ! is_dir( $parent ) && dirname( $parent ) !== $parent ) {
$parent = dirname( $parent );
}
if ( empty( $parent ) ) {
return false;
}
$perms = self::get_permissions( $parent );
if ( ! is_int( $perms ) ) {
$perms = self::get_default_permissions();
}
$cached_umask = umask( 0 );
$result = @mkdir( $dir, $perms, true ); // phpcs:ignore -- We don't want to have fatalities here.
umask( $cached_umask );
if ( $result ) {
self::add_file_listing_protection( $dir );
return true;
}
return false;
}
/**
* Retrieves the full path to plugin's working directory. Returns a folder path with a trailing slash. It also
* creates the folder unless the $skip_creation parameter is set to true.
*
* Default path is "{uploads folder}/self::WP2FA_UPLOADS_DIR/"
*
* @param string $path Optional path relative to the working directory.
* @param bool $skip_creation If true, the folder will not be created.
* @param bool $ignore_site If true, there will be no sub-site specific subfolder in multisite context.
*
* @return string|\WP_Error
*
* @since 2.6.0
*/
public static function get_upload_path( $path = '', $skip_creation = false, $ignore_site = false ) {
$result = '';
$upload_dir = wp_upload_dir( null, false );
if ( is_array( $upload_dir ) && array_key_exists( 'basedir', $upload_dir ) ) {
$result = $upload_dir['basedir'] . \DIRECTORY_SEPARATOR . self::WP2FA_UPLOADS_DIR . \DIRECTORY_SEPARATOR;
} elseif ( defined( 'WP_CONTENT_DIR' ) ) {
// Fallback in case there is a problem with filesystem.
$result = WP_CONTENT_DIR . \DIRECTORY_SEPARATOR . 'uploads' . \DIRECTORY_SEPARATOR . self::WP2FA_UPLOADS_DIR . \DIRECTORY_SEPARATOR;
}
if ( empty( $result ) ) {
// Empty result here means invalid custom path or a problem with WordPress (uploads folder issue or mission WP_CONTENT_DIR).
return new \WP_Error( '2fa_uplaods_dir_missing', __( 'The base of WSAL working directory cannot be determined. Custom path is invalid or there is some other issue with your WordPress installation.', 'wp-2fa' ) );
}
// Append site specific subfolder in multisite context.
if ( ! $ignore_site && WP_Helper::is_multisite() ) {
$site_id = \get_current_blog_id();
if ( $site_id > 0 ) {
$result .= 'sites' . \DIRECTORY_SEPARATOR . $site_id . \DIRECTORY_SEPARATOR;
}
}
// Append optional path passed as a parameter.
if ( $path && is_string( $path ) ) {
$result .= $path . \DIRECTORY_SEPARATOR;
}
if ( ! file_exists( $result ) ) {
if ( ! $skip_creation ) {
if ( ! \wp_mkdir_p( $result ) ) {
return new \WP_Error(
'mkdir_failed',
sprintf(
/* translators: %s: Directory path. */
__( 'Unable to create directory %s. Is its parent directory writable by the server?', 'wp-2fa' ),
esc_html( $result )
)
);
}
}
self::add_file_listing_protection( $result );
}
return $result;
}
/**
* Remove the supplied file.
*
* @param string $file - The name of the file and path.
*
* @return bool|WP_Error Boolean true on success or a WP_Error object if an error occurs.
*
* @since 2.6.0
*/
public static function remove( $file ) {
if ( ! self::exists( $file ) ) {
return true;
}
if ( ! PHP_Helper::is_callable( 'unlink' ) ) {
return new \WP_Error(
'wp-2fa',
// translators: the name of the file.
sprintf( __( 'The file %s could not be removed as the unlink() function is disabled. This is a system configuration issue.', 'wp-2fa' ), $file )
);
}
$result = @unlink( $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.unlink_unlink
@clearstatcache( true, $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
if ( $result ) {
return true;
}
return new \WP_Error(
'wp-2fa',
sprintf(
// translators: the name of the file.
__( 'Unable to remove %s due to an unknown error.', 'wp-2fa' ),
$file
)
);
}
/**
* Gets the content of a file
*
* @param string $file - The name of the file.
*
* @return bool|string
*
* @since 2.4.0
*/
protected static function get_file_contents( string $file ) {
if ( ! self::exists( $file ) ) {
return '';
}
$contents = self::read( $file );
if ( is_wp_error( $contents ) ) {
return false;
}
return $contents;
}
/**
* Write the supplied modification to the wp-config.php file.
*
* @since 2.4.0
*
* @param string $modification - The modification to add to the wp-config.php file.
*
* @return bool
*/
private static function write_wp_config( $modification ) {
$file_path = self::get_wp_config_file_path();
return self::update( $file_path, $modification );
}
/**
* Updates the content of a file
*
* @param string $file - The name of the file to update.
* @param string $modification - The modification to be added to the file.
*
* @return boolean
*
* @since 2.4.0
*/
private static function update( string $file, string $modification ): bool {
// Check to make sure that the settings give permission to write files.
if ( ! self::can_write_to_file( $file ) ) {
return false;
}
$contents = self::read( $file );
if ( is_wp_error( $contents ) ) {
return $contents;
}
if ( ! $contents ) {
return false;
}
$modification = ltrim( $modification, "\x0B\r\n\0" );
$modification = rtrim( $modification, " \t\x0B\r\n\0" );
if ( empty( $modification ) ) {
// If there isn't a new modification, write the content without any modification and return the result.
if ( empty( $contents ) ) {
$contents = PHP_EOL;
}
return false;
}
$placeholder = self::get_placeholder();
// Ensure that the generated placeholder can be uniquely identified in the contents.
while ( false !== strpos( $contents, $placeholder ) ) {
$placeholder = self::get_placeholder();
}
// Put the placeholder at the beginning of the file, after the <?php tag.
$contents = preg_replace( '/^(.*?<\?(?:php)?)\s*(?:\r\r\n|\r\n|\r|\n)/', "\${1}$placeholder", $contents, 1 );
if ( false === strpos( $contents, $placeholder ) ) {
$contents = preg_replace( '/^(.*?<\?(?:php)?)\s*(.+(?:\r\r\n|\r\n|\r|\n))/', "\${1}$placeholder$2", $contents, 1 );
}
if ( false === strpos( $contents, $placeholder ) ) {
$contents = "<?php$placeholder?" . ">$contents";
}
// Pad away from existing sections when adding iThemes Security modifications.
$line_ending = self::get_line_ending( $contents );
while ( ! preg_match( "/(?:^|(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n)(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n))$placeholder/", $contents ) ) {
$contents = preg_replace( "/$placeholder/", "$line_ending$placeholder", $contents );
}
while ( ! preg_match( "/$placeholder(?:$|(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n)(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n))/", $contents ) ) {
$contents = preg_replace( "/$placeholder/", "$placeholder$line_ending", $contents );
}
// Ensure that the file ends in a newline if the placeholder is at the end.
$contents = preg_replace( "/$placeholder$/", "$placeholder$line_ending", $contents );
if ( ! empty( $modification ) ) {
// Normalize line endings of the modification to match the file's line endings.
$modification = self::normalize_line_endings( $modification, $line_ending );
// Exchange the placeholder with the modification.
$contents = preg_replace( "/$placeholder/", $modification, $contents );
}
// Write the new contents to the file and return the results.
return self::write( $file, $contents );
}
/**
* Generates unique placeholder to be used in the string
*
* @return string
*
* @since 2.4.0
*/
private static function get_placeholder(): string {
$characters = str_split( 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' );
$string = '';
for ( $x = 0; $x < 100; $x++ ) {
$string .= array_rand( $characters );
}
return $string;
}
/**
* Returns to proper line endings of a given content
*
* @param string $contents - The text to be checked.
*
* @return string
*
* @since 2.4.0
*/
private static function get_line_ending( string $contents ) {
if ( empty( $contents ) ) {
return PHP_EOL;
}
$count["\n"] = preg_match_all( "/(?<!\r)\n/", $contents, $matches );
$count["\r"] = preg_match_all( "/\r(?!\n)/", $contents, $matches );
$count["\r\n"] = preg_match_all( "/(?<!\r)\r\n/", $contents, $matches );
$count["\r\r\n"] = preg_match_all( "/\r\r\n/", $contents, $matches );
if ( 0 === array_sum( $count ) ) {
return PHP_EOL;
}
$maxes = array_keys( $count, max( $count ), true );
if ( in_array( "\r\r\n", $maxes, true ) ) {
return "\r\r\n";
}
return $maxes[0];
}
/**
* Normalizing fileendings for different platforms
*
* @param string $content - The file content to be checked.
* @param string $line_ending - Line endings to be used.
*
* @return string
*
* @since 2.4.0
*/
private static function normalize_line_endings( string $content, string $line_ending = "\n" ): string {
return preg_replace( '/(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n/', $line_ending, $content );
}
/**
* Reads the content of a file
*
* @param string $file - The file to read.
*
* @return bool|string
*
* @since 2.4.0
*/
private static function read( string $file ) {
if ( ! is_file( $file ) ) {
return false;
}
$callable = array();
if ( PHP_Helper::is_callable( 'file_get_contents' ) ) {
$callable[] = 'file_get_contents';
}
if ( PHP_Helper::is_callable( 'fopen' ) && PHP_Helper::is_callable( 'feof' ) && PHP_Helper::is_callable( 'fread' ) && PHP_Helper::is_callable( 'flock' ) ) {
$callable[] = 'fopen';
}
if ( empty( $callable ) ) {
return false;
}
$contents = false;
// Different permissions to try in case the starting set of permissions are prohibiting read.
$trial_perms = array(
false,
0644,
0664,
0666,
);
foreach ( $trial_perms as $perms ) {
if ( false !== $perms ) {
if ( ! isset( $original_file_perms ) ) {
$original_file_perms = self::get_permissions( $file );
}
self::chmod( $file, $perms );
}
if ( in_array( 'fopen', $callable, true ) ) {
if ( false !== ( $fh = fopen( $file, 'rb' ) ) ) { // phpcs:ignore -- Ignored the assigned on the same line error
flock( $fh, LOCK_SH );
$contents = '';
while ( ! feof( $fh ) ) {
$contents .= fread( $fh, 1024 ); // phpcs:ignore -- Ignored the file operation notification
}
flock( $fh, LOCK_UN );
fclose( $fh ); // phpcs:ignore -- Ignored the file operation notification
}
}
if ( ( false === $contents ) && in_array( 'file_get_contents', $callable, true ) ) {
$contents = file_get_contents( $file ); // phpcs:ignore -- Ignored the wp_remote_get usage
}
if ( false !== $contents ) {
if ( isset( $original_file_perms ) && is_int( $original_file_perms ) ) {
// Reset the original file permissions if they were modified.
self::chmod( $file, $original_file_perms );
}
return $contents;
}
}
return false;
}
/**
* Changes the permissions of a file
*
* @param string $file - The file to change permissions to.
* @param mixed $perms - The permissions to be set.
*
* @return bool
*
* @since 2.4.0
*/
private static function chmod( string $file, $perms ): bool {
if ( ! is_int( $perms ) ) {
return \CURLOPT_SSL_FALSESTART;
}
if ( ! PHP_Helper::is_callable( 'chmod' ) ) {
return false;
}
return @chmod( $file, $perms ); // phpcs:ignore -- Don't need fatalities here.
}
/**
* Returns the default filesystem permissions
*
* @return integer
*
* @since 2.4.0
*/
private static function get_default_permissions() {
$perms = self::get_permissions( ABSPATH );
if ( ! is_wp_error( $perms ) ) {
return $perms;
}
return 0755;
}
}
}
includes/classes/Admin/Helpers/class-wp-helper.php 0000644 00000020263 15075513060 0016165 0 ustar 00 <?php
/**
* Responsible for the WP core functionalities.
*
* @package wp2fa
* @subpackage helpers
*
* @since 2.2.0
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Helpers;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/*
* WP helper class
*/
if ( ! class_exists( '\WP2FA\Admin\Helpers\WP_Helper' ) ) {
/**
* All the WP functionality must go trough this class.
*
* @since 2.2.0
*/
class WP_Helper {
/**
* Hold the user roles as array - Human readable is used for key of the array, and the internal role name is the value.
*
* @var array
*
* @since 2.2.0
*/
private static $user_roles = array();
/**
* Hold the user roles as array - Internal role name is used for key of the array, and the human readable format is the value.
*
* @var array
*
* @since 2.2.0
*/
private static $user_roles_wp = array();
/**
* Keeps the value of the multisite install of the WP.
*
* @var bool
*
* @since 2.2.0
*/
private static $is_multisite = null;
/**
* Holds array with all the sites in multisite WP installation.
*
* @var array
*/
private static $sites = array();
/**
* Inits the class, and fires all the necessarily methods.
*
* @return void
*
* @since 2.2.0
*/
public static function init() {
if ( self::is_multisite() ) {
\add_action( 'network_admin_notices', array( __CLASS__, 'show_critical_admin_notice' ) );
} else {
\add_action( 'admin_notices', array( __CLASS__, 'show_critical_admin_notice' ) );
}
}
/**
* Checks if specific role exists.
*
* @param string $role - The name of the role to check.
*
* @since 2.2.0
*/
public static function is_role_exists( string $role ): bool {
self::set_roles();
if ( in_array( $role, self::$user_roles, true ) ) {
return true;
}
return false;
}
/**
* Returns the currently available WP roles - the Human readable format is the key.
*
* @return array
*
* @since 2.2.0
*/
public static function get_roles() {
self::set_roles();
return self::$user_roles;
}
/**
* Returns the currently available WP roles.
*
* @return array
*
* @since 2.2.0
*/
public static function get_roles_wp() {
if ( empty( self::$user_roles_wp ) ) {
self::set_roles();
self::$user_roles_wp = array_flip( self::$user_roles );
}
return self::$user_roles_wp;
}
/**
* Shows critical notices to the admin.
*
* @return void
*
* @since 2.2.0
*/
public static function show_critical_admin_notice() {
if ( User_Helper::is_admin() ) {
/*
* Gives the ability to show notices to the admins
*/
\do_action( WP_2FA_PREFIX . 'critical_notice' );
}
}
/**
* Check is this is a multisite setup.
*
* @return bool
*
* @since 2.2.0
*/
public static function is_multisite() {
if ( null === self::$is_multisite ) {
self::$is_multisite = function_exists( 'is_multisite' ) && is_multisite();
}
return self::$is_multisite;
}
/**
* Collects all the sites from multisite WP installation.
*
* @since 2.5.0
*/
public static function get_multi_sites(): array {
if ( self::is_multisite() ) {
if ( empty( self::$sites ) ) {
self::$sites = \get_sites();
}
return self::$sites;
}
return array();
}
/**
* Calculating the signature.
*
* @param array $data - Array with data to create a signature for.
*
* @since 2.2.2
*/
public static function calculate_api_signature( array $data ): string {
$now = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
$nonce = $now->getTimestamp();
$pk_hash = hash( 'sha512', $data['license_key'] . '|' . $nonce );
$authentication_string = base64_encode( $pk_hash . '|' . $nonce );
return $authentication_string;
}
/**
* Checks if that is the WP login page or not.
*
* @return bool
*
* @since 2.4.1
*/
public static function is_wp_login() {
$abs_path = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, ABSPATH );
if ( function_exists( 'is_account_page' ) && is_account_page() ) {
// The user is on the WooCommerce login page.
return true;
}
return ( in_array( $abs_path . 'wp-login.php', get_included_files() ) || in_array( $abs_path . 'wp-register.php', get_included_files() ) ) || ( isset( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) || '/wp-login.php' == $_SERVER['PHP_SELF']; // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
}
/**
* Check whether we are on an admin and plugin page.
*
* @since 2.4.1
*
* @param array|string $slug ID(s) of a plugin page. Possible values: 'general', 'logs', 'about' or array of them.
*
* @return bool
*/
public static function is_admin_page( $slug = array() ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded
$cur_page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$check = WP_2FA_PREFIX_PAGE;
return \is_admin() && ( false !== strpos( $cur_page, $check ) );
}
/**
* Remove all non-WP Mail SMTP plugin notices from our plugin pages.
*
* @since 2.4.1
*/
public static function hide_unrelated_notices() {
// Bail if we're not on our screen or page.
if ( ! self::is_admin_page() ) {
return;
}
self::remove_unrelated_actions( 'user_admin_notices' );
self::remove_unrelated_actions( 'admin_notices' );
self::remove_unrelated_actions( 'all_admin_notices' );
self::remove_unrelated_actions( 'network_admin_notices' );
}
/**
* Creates a nonce for HTML field by given name.
*
* @param string $nonce_name -The name of the nonce to create.
*
* @return string
*
* @since 2.6.0
*/
public static function create_data_nonce( string $nonce_name ): string {
return ' data-nonce="' . \esc_attr( \wp_create_nonce( $nonce_name ) ) . '"';
}
/**
* Extracts the domain part of the given string.
*
* @param string $url_to_check - The URL string to be checked.
*
* @return string
*
* @since 2.6.0
*/
public static function extract_domain( string $url_to_check ): string {
// get the full domain.
// $urlparts = parse_url( \site_url() );.
if ( false !== strpos( $url_to_check, '@' ) ) {
$domain = \explode( '@', $url_to_check )[1];
return $domain;
}
$urlparts = parse_url( $url_to_check );
$domain = $urlparts ['host'];
// get the TLD and domain.
$domainparts = explode( '.', $domain );
$domain = $domainparts[ count( $domainparts ) - 2 ] . '.' . $domainparts[ count( $domainparts ) - 1 ];
return $domain;
}
/**
* Remove all non-WP Mail SMTP notices from the our plugin pages based on the provided action hook.
*
* @since 2.4.1
*
* @param string $action The name of the action.
*/
private static function remove_unrelated_actions( $action ) {
global $wp_filter;
if ( empty( $wp_filter[ $action ]->callbacks ) || ! is_array( $wp_filter[ $action ]->callbacks ) ) {
return;
}
foreach ( $wp_filter[ $action ]->callbacks as $priority => $hooks ) {
foreach ( $hooks as $name => $arr ) {
if (
( // Cover object method callback case.
is_array( $arr['function'] ) &&
isset( $arr['function'][0] ) &&
is_object( $arr['function'][0] ) &&
false !== strpos( ( get_class( $arr['function'][0] ) ), 'WP2FA' )
) ||
( // Cover class static method callback case.
! empty( $name ) &&
false !== strpos( ( $name ), 'WP2FA' )
)
) {
continue;
}
unset( $wp_filter[ $action ]->callbacks[ $priority ][ $name ] );
}
}
}
/**
* Sets the internal variable with all the existing WP roles.
*
* @return void
*
* @since 2.2.0
*/
private static function set_roles() {
if ( empty( self::$user_roles ) ) {
global $wp_roles;
if ( null === $wp_roles ) {
wp_roles();
}
self::$user_roles = array_flip( $wp_roles->get_names() );
}
}
}
}
includes/classes/Admin/Helpers/class-methods-helper.php 0000644 00000014010 15075513060 0017173 0 ustar 00 <?php
/**
* Responsible for the User's operations
*
* @package wp2fa
* @subpackage helpers
* @since 2.6.0
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Helpers;
use WP2FA\Admin\Helpers\Classes_Helper;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* User's settings class
*/
if ( ! class_exists( '\WP2FA\Admin\Helpers\Methods_Helper' ) ) {
/**
* All the user related settings must go trough this class.
*
* @since 2.6.0
*/
class Methods_Helper {
const METHODS_NAMESPACE = '\WP2FA\Methods';
const POLICY_SETTINGS_NAME = 'methods_order';
/**
* Cached methods array
*
* @var array
*
* @since 2.7.0
*/
public static $methods = array();
/**
* Inits the class and initializes all the methods
*
* @return void
*
* @since 2.6.0
*/
public static function init() {
foreach ( self::get_methods() as $method ) {
if ( method_exists( $method, 'init' ) ) {
call_user_func_array( array( $method, 'init' ), array() );
}
}
\add_action( WP_2FA_PREFIX . 'methods_setup', array( __CLASS__, 'methods_settings' ), 10, 3 );
\add_filter( WP_2FA_PREFIX . 'filter_output_content', array( __CLASS__, 'settings_store' ), 10, 2 );
\add_action( WP_2FA_PREFIX . 'methods_options', array( __CLASS__, 'methods_options' ) );
\add_action( WP_2FA_PREFIX . 'methods_reconfigure_options', array( __CLASS__, 'methods_re_configure' ) );
}
/**
* Sets the methods in correct order for re-configuring. Checks the selected method for the user and puts it on top of the list
*
* @return void
*
* @since 2.6.0
*/
public static function methods_re_configure() {
$role = User_Helper::get_user_role();
/**
* Option to re-configure the methods - all the methods are called and their order and code is collected. Then the currently selected method is positioned on top and methods are shown in order. That is called in the user profile page.
*
* @param array - All the collected methods and their order.
* @param string $role - The role of the current user
*
* @since 2.6.0
*/
$methods = \apply_filters( WP_2FA_PREFIX . 'methods_re_configure', array(), $role );
$enabled_method = User_Helper::get_enabled_method_for_user();
foreach ( $methods as $order => $method ) {
if ( $enabled_method === $method['name'] ) {
$methods[-1] = $method;
unset( $methods[ $order ] );
break;
}
}
ksort( $methods );
foreach ( $methods as $method ) {
echo $method['output']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Collects the methods options and shows them in order
*
* @return void
*
* @since 2.6.0
*/
public static function methods_options() {
$role = User_Helper::get_user_role();
/**
* Shows methods in order. Every method is called and its code and order is collected. That is used when there are no methods selected from the user.
*
* @param array - All the collected methods and their order.
* @param string $role - The role of the current user
*
* @since 2.6.0
*/
$methods = \apply_filters( WP_2FA_PREFIX . 'methods_modal_options', array(), $role );
ksort( $methods );
foreach ( $methods as $method ) {
echo $method; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Settings page and first time wizard settings render
*
* @param boolean $setup_wizard - Is that the first time setup wizard.
* @param string $data_role - Additional HTML data attribute.
* @param mixed $role - Name of the role.
*
* @return void
*
* @since 2.6.0
*/
public static function methods_settings( bool $setup_wizard, string $data_role, $role = null ) {
/**
* Shows methods in order. Every method is called and its code and order is collected. Used in the wizards.
*
* @param array - All the collected methods and their order.
* @param bool - Is that a setup wizard call or not?
* @param string - Additional HTML data attribute.
* @param string $role - The role, that is when global settings of the plugin are selected.
*
* @since 2.6.0
*/
$methods = \apply_filters( WP_2FA_PREFIX . 'methods_settings', array(), $setup_wizard, $data_role, $role );
ksort( $methods );
foreach ( $methods as $method ) {
echo $method; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Adds and filters extension values in the settings store array ($output).
*
* @param array $output - Array with the currently stored settings.
* @param array $input - Array with the input ($_POST) values.
*
* @return array
*
* @since 2.6.0
*/
public static function settings_store( array $output, array $input ) {
if ( isset( $input[ self::POLICY_SETTINGS_NAME ] ) && \is_array( $input[ self::POLICY_SETTINGS_NAME ] ) ) {
foreach ( $input[ self::POLICY_SETTINGS_NAME ] as $order => $method ) {
$output[ self::POLICY_SETTINGS_NAME ][ $order ] = $method;
}
}
return $output;
}
/**
* Returns the method by its slug.
*
* @param string $provider_name - The slug to search for.
*
* @return bool|\WP2FA\Methods
*
* @since 2.7.0
*/
public static function get_method_by_provider_name( string $provider_name ) {
foreach ( self::get_methods() as $method ) {
if ( $provider_name === $method::METHOD_NAME ) {
return $method;
}
}
return \false;
}
/**
* Returns all of the registered methods.
*
* @return array
*
* @since 2.7.0
*/
private static function get_methods(): array {
if ( empty( self::$methods ) ) {
/**
* Gives the ability to add classes to the Class_Helper array.
*
* @since 2.7.0
*/
\do_action( WP_2FA_PREFIX . 'add_to_class_map' );
self::$methods = Classes_Helper::get_classes_by_namespace( self::METHODS_NAMESPACE );
}
return self::$methods;
}
}
}
includes/classes/Admin/Helpers/class-php-helper.php 0000644 00000001660 15075513060 0016326 0 ustar 00 <?php
/**
* Responsible for the User's operations
*
* @package wp2fa
* @subpackage helpers
* @since 2.4.0
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Helpers;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* User's settings class
*/
if ( ! class_exists( '\WP2FA\Admin\Helpers\PHP_Helper' ) ) {
/**
* All the user related settings must go trough this class.
*
* @since 2.4.0
*/
class PHP_Helper {
/**
* Checks if given function is callable (exists) or not
*
* @param string $function_name - The name of the function to check.
*
* @return boolean
*
* @since 2.4.0
*/
public static function is_callable( string $function_name ): bool {
if ( ! is_callable( $function_name ) ) {
return false;
}
return true;
}
}
}
includes/classes/Admin/Helpers/index.php 0000644 00000000046 15075513060 0014263 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/Helpers/class-ajax-helper.php 0000644 00000027552 15075513060 0016472 0 ustar 00 <?php
/**
* Responsible for the AJAX calls.
*
* @package wp2fa
* @subpackage helpers
*
* @since 2.6.0
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin\Helpers;
use WP2FA\WP2FA;
use WP2FA\Utils\User_Utils;
use WP2FA\Admin\Settings_Page;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\SettingsPages\Settings_Page_Email;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( '\WP2FA\Admin\Helpers\Ajax_Helper' ) ) {
/**
* Responsible for the proper AJAX calls and responses.
*/
class Ajax_Helper {
/**
* Get all users in AJAX matter and returns them
*
* @SuppressWarnings(PHPMD.ExitExpression)
*
* @since 2.6.0
*/
public static function get_all_users() {
// Die if user does not have permission to view.
if ( ! current_user_can( 'manage_options' ) ) {
die( 'Access Denied.' );
}
// Filter $_GET array for security.
$get_array = filter_input_array( INPUT_GET );
// Die if nonce verification failed.
if ( ! wp_verify_nonce( sanitize_text_field( $get_array['wp_2fa_nonce'] ), 'wp-2fa-settings-nonce' ) ) {
die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
}
$users_args = array(
'fields' => array( 'ID', 'user_login' ),
);
if ( WP_Helper::is_multisite() ) {
$users_args['blog_id'] = 0;
}
$users_data = User_Utils::get_all_user_ids_and_login_names( 'query', $users_args );
// Create final array which we will fill in below.
$users = array();
foreach ( $users_data as $user ) {
if ( stripos( $user['user_login'], $get_array['term'] ) !== false ) {
array_push(
$users,
array(
'value' => $user['user_login'],
'label' => $user['user_login'],
)
);
}
}
echo wp_json_encode( $users );
exit;
}
/**
* Get all network sites in AJAX way
*
* @SuppressWarnings(PHPMD.ExitExpression)
*
* @since 2.6.0
*/
public static function get_all_network_sites() {
// Die if user does not have permission to view.
if ( ! current_user_can( 'manage_options' ) ) {
die( 'Access Denied.' );
}
// Filter $_GET array for security.
$get_array = filter_input_array( INPUT_GET );
// Die if nonce verification failed.
if ( ! wp_verify_nonce( sanitize_text_field( $get_array['wp_2fa_nonce'] ), 'wp-2fa-settings-nonce' ) ) {
die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
}
// Fetch sites.
$sites_found = array();
foreach ( get_sites() as $site ) {
$subsite_id = get_object_vars( $site )['blog_id'];
$subsite_name = get_blog_details( $subsite_id )->blogname;
$site_details = '';
$site_details[ $subsite_id ] = $subsite_name;
if ( false !== stripos( $subsite_name, $get_array['term'] ) ) {
array_push(
$sites_found,
array(
'label' => $subsite_id,
'value' => $subsite_name,
)
);
}
}
echo wp_json_encode( $sites_found );
exit;
}
/**
* Unlock users accounts if they have overrun grace period it ia also used in AJAX calls
*
* @param int $user_id User ID.
*
* @SuppressWarnings(PHPMD.ExitExpression)
*
* @since 2.6.0
*/
public static function unlock_account( $user_id ) {
// Die if user does not have permission to view.
if ( ! current_user_can( 'manage_options' ) ) {
die( 'Access Denied.' );
}
$grace_period = WP2FA::get_wp2fa_setting( 'grace-period' );
$grace_period_denominator = WP2FA::get_wp2fa_setting( 'grace-period-denominator' );
$create_a_string = $grace_period . ' ' . $grace_period_denominator;
// Turn that string into a time.
$grace_expiry = strtotime( $create_a_string );
// Filter $_GET array for security.
$get_array = filter_input_array( INPUT_GET );
$nonce = sanitize_text_field( $get_array['wp_2fa_nonce'] );
// Die if nonce verification failed.
if ( ! wp_verify_nonce( $nonce, 'wp-2fa-unlock-account-nonce' ) ) {
die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
}
if ( isset( $get_array['user_id'] ) ) {
User_Helper::remove_meta( WP_2FA_PREFIX . 'locked_account_notification', intval( $get_array['user_id'] ) );
User_Helper::remove_grace_period( intval( $get_array['user_id'] ) );
User_Helper::set_user_expiry_date( (string) $grace_expiry, intval( $get_array['user_id'] ) );
Settings_Page::send_account_unlocked_email( intval( $get_array['user_id'] ) );
/*
* Fires after the user is unlocked.
*
* @param \WP_User $user - The user for which the method has been set.
*
* @since 2.6.0
*/
\do_action( WP_2FA_PREFIX . 'user_is_unlocked', User_Helper::get_user( intval( $get_array['user_id'] ) ) );
\add_action( 'admin_notices', array( __CLASS__, 'user_unlocked_notice' ) );
}
}
/**
* Sets the salt key into the wp-config.php file via AJAX request.
*
* @return void
*
* @since 2.4.0
*/
public static function set_salt_key() {
if ( \wp_doing_ajax() ) {
if ( isset( $_REQUEST['_wpnonce'] ) ) {
$nonce_check = \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) ), 'wp-2fa-set-salt-nonce' );
if ( ! $nonce_check ) {
\wp_send_json_error( new \WP_Error( 500, \esc_html__( 'Nonce checking failed', 'wp-2fa' ) ), 400 );
} elseif ( \current_user_can( 'manage_options' ) ) {
if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
\wp_send_json_error(
new \WP_Error(
500,
\esc_html__(
'Unable to write to wp-config.php',
'wp-2fa'
)
),
400
);
} else {
$secret_key = Settings_Utils::get_option( 'secret_key' );
if ( ! empty( $secret_key ) ) {
File_Writer::save_secret_key( $secret_key );
Settings_Utils::delete_option( 'secret_key' );
\wp_send_json_success(
\esc_html__(
'wp-config.php successfully update, global setting deleted',
'wp-2fa'
)
);
} else {
\wp_send_json_error(
new \WP_Error(
500,
\esc_html__(
'Unable to find global secret key',
'wp-2fa'
)
),
400
);
}
}
}
}
}
}
/**
* Remove user 2fa config
*
* @param int $user_id User ID.
*
* @SuppressWarnings(PHPMD.ExitExpression)
*
* @since 2.6.0
*/
public static function remove_user_2fa( $user_id ) {
// Filter $_GET array for security.
$get_array = filter_input_array( INPUT_GET );
$nonce = sanitize_text_field( $get_array['wp_2fa_nonce'] );
if ( ! wp_verify_nonce( $nonce, 'wp-2fa-remove-user-2fa-nonce' ) ) {
die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
}
if ( isset( $get_array['user_id'] ) ) {
$user_id = intval( $get_array['user_id'] );
if ( ! current_user_can( 'manage_options' ) && get_current_user_id() !== $user_id ) {
return;
}
User_Helper::remove_2fa_for_user( $user_id );
if ( isset( $get_array['admin_reset'] ) ) {
add_action( 'admin_notices', array( __CLASS__, 'admin_deleted_2fa_notice' ) );
} else {
add_action( 'admin_notices', array( __CLASS__, 'user_deleted_2fa_notice' ) );
}
}
}
/**
* Returns the user roles in AJAX matter.
*
* @return void
*
* @since 2.6.0
*/
public static function get_ajax_user_roles() {
if ( \wp_doing_ajax() ) {
// Filter $_GET array for security.
$get_array = filter_input_array( INPUT_GET );
// Die if nonce verification failed.
if ( ! wp_verify_nonce( sanitize_text_field( $get_array['wp_2fa_nonce'] ), 'wp-2fa-settings-nonce' ) ) {
die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
}
$roles = array();
foreach ( WP_Helper::get_roles_wp() as $role => $human_readable ) {
if ( stripos( $human_readable, $get_array['term'] ) !== false ) {
array_push(
$roles,
array(
'label' => $role,
'value' => $human_readable,
)
);
}
}
echo wp_json_encode( $roles );
exit;
}
}
/**
* Handles AJAX calls for sending test emails.
*
* @return void
*
* @since 2.6.0
*/
public static function handle_send_test_email_ajax() {
// check user permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error();
}
// check email id.
$email_id = isset( $_POST['email_id'] ) ? sanitize_text_field( \wp_unslash( $_POST['email_id'] ) ) : null;
if ( null === $email_id || false === $email_id ) {
wp_send_json_error();
}
// check nonce.
$nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ) ) : null;
if ( null === $nonce || false === $nonce || ! wp_verify_nonce( $nonce, 'wp-2fa-email-test-' . $email_id ) ) {
wp_send_json_error();
}
$user_id = get_current_user_id();
// Grab user data.
$user = get_userdata( $user_id );
// Grab user email.
$email = $user->user_email;
if ( 'config_test' === $email_id ) {
$email_sent = Settings_Page::send_email(
$email,
esc_html__( 'Test email from WP 2FA', 'wp-2fa' ),
esc_html__( 'This email was sent by the WP 2FA plugin to test the email delivery.', 'wp-2fa' )
);
if ( $email_sent ) {
wp_send_json_success( 'Test email was successfully sent to <strong>' . $email . '</strong>' );
}
wp_send_json_error();
}
/**
* All email templates
*
* @var Email_Template[] $email_templates
*/
$email_templates = Settings_Page_Email::get_email_notification_definitions();
foreach ( $email_templates as $email_template ) {
if ( $email_id === $email_template->get_email_content_id() ) {
// send the test email.
// Setup the email contents.
$subject = wp_strip_all_tags( \WP2FA\WP2FA::replace_email_strings( \WP2FA\WP2FA::get_wp2fa_email_templates( $email_id . '_email_subject' ) ) );
$message = wpautop( \WP2FA\WP2FA::replace_email_strings( \WP2FA\WP2FA::get_wp2fa_email_templates( $email_id . '_email_body' ), $user_id ) );
$email_sent = Settings_Page::send_email( $email, $subject, $message );
if ( $email_sent ) {
wp_send_json_success( 'Test email <strong>' . $email_template->get_title() . '</strong> was successfully sent to <strong>' . $email . '</strong>' );
}
wp_send_json_error();
}
}
}
/**
* User deleted 2FA settings notification
*
* @since 2.6.0
*/
public static function user_deleted_2fa_notice() {
?>
<div class="notice notice-success is-dismissible">
<p><?php esc_html_e( 'Your 2FA settings have been removed.', 'wp-2fa' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
/**
* Admin deleted user 2FA settings notification
*
* @since 2.6.0
*/
public static function admin_deleted_2fa_notice() {
?>
<div class="notice notice-success is-dismissible">
<p><?php esc_html_e( 'User 2FA settings have been removed.', 'wp-2fa' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
/**
* User unlocked notice.
*
* @since 2.6.0
*/
public static function user_unlocked_notice() {
?>
<div class="notice notice-success is-dismissible">
<p><?php esc_html_e( 'User account successfully unlocked. User can login again.', 'wp-2fa' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
}
}
includes/classes/Admin/Helpers/class-user-helper.php 0000644 00000172647 15075513060 0016533 0 ustar 00 <?php
/**
* Responsible for the User's operations.
*
* @package wp2fa
* @subpackage helpers
*
* @since 2.2.0
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Admin\Helpers;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
use wpdb;
use WP2FA\WP2FA;
use WP2FA\Utils\User_Utils;
use WP2FA\Extensions_Loader;
use WP2FA\Admin\Settings_Page;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Freemius\User_Licensing;
use WP2FA\Admin\Controllers\Methods;
use WP2FA\Admin\Controllers\Settings;
/*
* User's settings class
*/
if ( ! class_exists( '\WP2FA\Admin\Helpers\User_Helper' ) ) {
/**
* All the user related settings must go trough this class.
*
* @since 2.2.0
*/
class User_Helper {
/**
* Enabled 2fa method for user meta name.
*/
public const ENABLED_METHODS_META_KEY = WP_2FA_PREFIX . 'enabled_methods';
/**
* Email token for user meta name.
*/
public const TOKEN_META_KEY = WP_2FA_PREFIX . 'email_token';
/**
* Global settings hash for user meta name
* That is used to check if user needs to be re-checked / re-configured, if the settings of the plugin are changed, probably the user settings also need to be changed - that meta holds the key to check against.
*/
public const USER_SETTINGS_HASH = WP_2FA_PREFIX . 'global_settings_hash';
/**
* The meta name for the user 2FA status in the plugin.
*/
public const USER_2FA_STATUS = WP_2FA_PREFIX . '2fa_status';
/**
* The user grace period expired meta key.
*/
public const USER_GRACE_KEY = WP_2FA_PREFIX . 'user_grace_period_expired';
/**
* The user grace period expiry date meta key.
*/
public const USER_GRACE_EXPIRY_KEY = WP_2FA_PREFIX . 'grace_period_expiry';
/**
* The user enforcement status.
*/
public const USER_ENFORCED_INSTANTLY = WP_2FA_PREFIX . 'user_enforced_instantly';
/**
* The user reconfigure 2fa status.
*/
public const USER_NEEDS_TO_RECONFIGURE_2FA = WP_2FA_PREFIX . 'user_needs_to_reconfigure_2fa';
/**
* The user enforcement state.
*/
public const USER_ENFORCEMENT_STATE = WP_2FA_PREFIX . 'enforcement_state';
/**
* The user nag dismissed flag.
*/
public const USER_NAG_DISMISSED = WP_2FA_PREFIX . 'update_nag_dismissed';
/**
* The default status of the user which has no status set yet.
*/
public const USER_UNDETERMINED_STATUS = 'no_determined_yet';
/**
* The last login date for the user.
*/
public const USER_LOGIN_DATE = 'login_date';
/**
* The reset password for the user is valid.
*/
public const USER_RESET_PASSWORD_VALID = 'reset_password_valid';
/**
* The nominated global email address for the user.
*/
public const USER_NOMINATED_EMAIL = WP_2FA_PREFIX . 'nominated_email_address';
/**
* The backup email address for the user - for backup methods when app is in use.
*/
public const USER_BACKUP_EMAIL = WP_2FA_PREFIX . 'backup_email_address';
/**
* The default user statuses.
*/
public const USER_STATE_STATUSES = array(
'optional',
'excluded',
'enforced',
);
/**
* The class user variable.
*
* @var \WP_User
*
* @since 2.2.0
*/
private static $user = null;
/**
* All global excluded roles
*
* @var array
*
* @since 2.5.0
*/
private static $excluded_roles = null;
/**
* All global excluded sites
*
* @var array
*
* @since 2.5.0
*/
private static $excluded_sites = null;
/**
* All global excluded users
*
* @var array
*
* @since 2.5.0
*/
private static $excluded_users = null;
/**
* All global included sites
*
* @var array
*
* @since 2.5.0
*/
private static $included_sites = null;
/**
* All global enforced users
*
* @var array
*
* @since 2.5.0
*/
private static $enforced_users = null;
/**
* All global enforced roles
*
* @var array
*
* @since 2.5.0
*/
private static $enforced_roles = null;
/**
* Marks the status of the updating process
*
* @var boolean
*
* @since 2.4.1
*/
private static $update_started = false;
/**
* Returns the enable 2fa backup methods for the given user
*
* @param \WP_User] $user - The user which has to be checked.
*
* @return mixed
*
* @since 2.6.0
*/
public static function get_enabled_backup_methods_for_user( $user = null ) {
self::set_proper_user( $user );
/*
* Checks the enabled methods for the user.
*
* @param mixed - Value of the method.
* @param array - Array of enabled methods for the user.
* @param \WP_User - The user which must be checked.
*
* @since 2.6.0
*/
return apply_filters( WP_2FA_PREFIX . 'user_enabled_backup_methods', array(), $user );
}
/**
* Returns the enabled 2FA method for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_enabled_method_for_user( $user = null ) {
self::set_proper_user( $user );
/*
* Checks the enabled methods for the user.
*
* @param mixed - Value of the method.
* @param string|null $user - Currently enabled method.
* @param \WP_User - The user which must be checked.
*
* @since 2.0.0
*/
return apply_filters( WP_2FA_PREFIX . 'user_enabled_methods', self::get_meta( self::ENABLED_METHODS_META_KEY ), $user );
}
/**
* Sets the enabled 2FA method for the user.
*
* @param string $method - The name of the method to set.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_enabled_method_for_user( string $method, $user = null ) {
self::set_proper_user( $user );
/*
* Fires before the user method is set.
*
* @param string - Current user method.
* @param \WP_User $user - The user for which the method has been set.
*
* @since 2.6.0
*/
\do_action( WP_2FA_PREFIX . 'before_method_been_set', self::get_enabled_method_for_user( self::get_user() ), self::get_user() );
$set_method = self::set_meta( self::ENABLED_METHODS_META_KEY, $method );
/*
* Fires when the user method is set.
*
* @param string - The method set for the user.
* @param \WP_User $user - The user for which the method has been set.
*
* @since 2.2.2
*/
\do_action( WP_2FA_PREFIX . 'method_has_been_set', $method, self::get_user() );
return $set_method;
}
/**
* Removes the 2FA method for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_enabled_method_for_user( $user = null ) {
self::set_proper_user( $user );
/*
* Fires before the user method is removed.
*
* @param string - Current user method.
* @param \WP_User $user - The user for which the method has been set.
*
* @since 2.6.0
*/
\do_action( WP_2FA_PREFIX . 'before_method_is_removed', self::get_enabled_method_for_user( self::get_user() ), self::get_user() );
self::remove_meta( self::ENABLED_METHODS_META_KEY, self::$user );
/*
* Fires after the user method is removed.
*
* @param \WP_User $user - The user for which the method has been set.
*
* @since 2.6.0
*/
\do_action( WP_2FA_PREFIX . 'after_method_is_removed', self::get_user() );
if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
if ( Extensions_Loader::use_proxytron() ) {
$user_blog_id = 1;
if ( WP_Helper::is_multisite() ) {
$user_blog_id = \get_active_blog_for_user( self::$user->ID )->blog_id;
}
if ( ( $current_blog = \get_current_blog_id() ) !== $user_blog_id ) { // phpcs:ignore
if ( WP_Helper::is_multisite() ) {
\switch_to_blog( $user_blog_id );
}
User_Licensing::method_has_been_set();
if ( WP_Helper::is_multisite() ) {
\switch_to_blog( $current_blog );
}
}
}
}
}
/**
* Returns the email token for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_email_token_for_user( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::TOKEN_META_KEY );
}
/**
* Sets the email token for the user.
*
* @param string $token - The token to set for the user.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_email_token_for_user( string $token, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::TOKEN_META_KEY, $token );
}
/**
* Removes the email token for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_email_token_for_user( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::TOKEN_META_KEY, self::$user );
}
/**
* Returns the last login date for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.5.0
*/
public static function get_login_date_for_user( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_RESET_PASSWORD_VALID );
}
/**
* Sets last login date for the user.
*
* @param bool $valid - The reset password is valid user.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.5.0
*/
public static function set_reset_password_valid_for_user( bool $valid, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_RESET_PASSWORD_VALID, $valid );
}
/**
* Removes last login date for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.5.0
*/
public static function remove_reset_password_valid_for_user( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_RESET_PASSWORD_VALID, self::$user );
}
/**
* Returns the last login date for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.5.0
*/
public static function get_reset_password_valid_for_user( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_RESET_PASSWORD_VALID );
}
/**
* Sets last login date for the user.
*
* @param int $login_date - The login date to set for the user.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.5.0
*/
public static function set_login_date_for_user( int $login_date, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_LOGIN_DATE, $login_date );
}
/**
* Removes last login date for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.5.0
*/
public static function remove_login_date_for_user( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_LOGIN_DATE, self::$user );
}
/**
* Returns the global settings hash for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_global_settings_hash_for_user( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_SETTINGS_HASH );
}
/**
* Sets the global settings hash for the user.
*
* @param string $hash - The global settings hash to set for the user.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_global_settings_hash_for_user( string $hash, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_SETTINGS_HASH, $hash );
}
/**
* Removes the global settings hash for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_global_settings_hash_for_user( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_SETTINGS_HASH, self::$user );
}
/**
* Returns the current 2FA status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_2fa_status( $user = null ) {
self::set_proper_user( $user );
$status = (string) self::get_meta( self::USER_2FA_STATUS );
if ( '' === trim( $status ) ) {
$status = self::USER_UNDETERMINED_STATUS;
self::set_2fa_status( self::USER_UNDETERMINED_STATUS );
}
return $status;
}
/**
* Sets the 2FA status for the user.
*
* @param string $status - The name of the status to set.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_2fa_status( string $status, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_2FA_STATUS, $status );
}
/**
* Removes the 2FA status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_2fa_status( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_2FA_STATUS, self::$user );
}
/**
* Returns the current nag status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.3
*/
public static function get_nag_status( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_NAG_DISMISSED );
}
/**
* Sets the nag status for the user.
*
* @param bool $status - The name of the status to set.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.3
*/
public static function set_nag_status( bool $status, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_NAG_DISMISSED, $status );
}
/**
* Removes the nag status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.3
*/
public static function remove_nag_status( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_NAG_DISMISSED, self::$user );
}
/**
* Returns the current 2FA status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_user_expiry_date( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_GRACE_EXPIRY_KEY );
}
/**
* Sets the 2FA status for the user.
*
* @param string $date - The period to set.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_user_expiry_date( string $date, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_GRACE_EXPIRY_KEY, $date );
}
/**
* Removes the 2FA status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_user_expiry_date( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_GRACE_EXPIRY_KEY, self::$user );
}
/**
* Returns the current 2FA enforcement status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_user_enforced_instantly( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_ENFORCED_INSTANTLY );
}
/**
* Sets the 2FA enforcement status for the user.
*
* @param bool $status - The status for user enforcement.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_user_enforced_instantly( bool $status, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_ENFORCED_INSTANTLY, $status );
}
/**
* Removes the 2FA enforcement status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_user_enforced_instantly( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_ENFORCED_INSTANTLY, self::$user );
}
/**
* Returns the current 2FA needs to reconfigure status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_user_needs_to_reconfigure_2fa( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_NEEDS_TO_RECONFIGURE_2FA );
}
/**
* Sets the 2FA needs to reconfigure status for the user.
*
* @param bool $status - The status for user enforcement.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_user_needs_to_reconfigure_2fa( bool $status, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_NEEDS_TO_RECONFIGURE_2FA, $status );
}
/**
* Removes the 2FA needs to reconfigure status for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_user_needs_to_reconfigure_2fa( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_NEEDS_TO_RECONFIGURE_2FA, self::$user );
}
/**
* Every meta call for the user must go through this method, so we can unify the code.
*
* @param string $meta - The meta name that we should check.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
* @param mixed $default_value - The default value to be returned if meta is not presented.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_meta( string $meta, $user = null, $default_value = true ) {
self::set_proper_user( $user );
return \get_user_meta( self::$user->ID, $meta, $default_value );
}
/**
* Every meta storing call for the user must go through this method.
*
* @param string $meta - The meta name that we should check.
* @param mixed $value - The value which should be stored.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_meta( string $meta, $value, $user = null ) {
self::set_proper_user( $user );
return \update_user_meta( self::$user->ID, $meta, $value );
}
/**
* Removes meta for the given user.
*
* @param string $meta - The name of the meta.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function remove_meta( string $meta, $user = null ) {
self::set_proper_user( $user );
return \delete_user_meta( self::$user->ID, $meta );
}
/**
* Returns the currently set user.
*
* @return \WP_User
*
* @since 2.2.0
*/
public static function get_user() {
if ( null === self::$user ) {
self::set_user();
}
return self::$user;
}
/**
* Returns WP User object.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return \WP_User
*
* @since 2.2.0
*/
public static function get_user_object( $user = null ) {
if ( null === $user && null !== self::$user ) {
return self::$user;
}
self::set_user( $user );
return self::$user;
}
/**
* Sets the user.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return void
*
* @since 2.2.0
*/
public static function set_user( $user = null ) {
if ( $user instanceof \WP_User ) {
if ( isset( self::$user ) && $user === self::$user ) {
return;
}
self::$user = $user;
} elseif ( false !== ( filter_var( $user, FILTER_VALIDATE_INT ) ) ) {
if ( isset( self::$user ) && $user instanceof \WP_User && $user === self::$user->ID ) {
return;
}
if ( ! function_exists( 'get_user_by' ) ) {
require ABSPATH . WPINC . '/pluggable.php';
}
self::$user = \get_user_by( 'id', $user );
if ( \is_bool( self::$user ) ) {
self::$user = \wp_get_current_user();
}
} elseif ( is_string( $user ) && ! empty( trim( (string) $user ) ) ) {
if ( isset( self::$user ) && $user instanceof \WP_User && $user === self::$user->ID ) {
return;
}
if ( ! function_exists( 'get_user_by' ) ) {
require ABSPATH . WPINC . '/pluggable.php';
}
self::$user = \get_user_by( 'login', $user );
} else {
if ( ! function_exists( 'wp_get_current_user' ) ) {
require ABSPATH . WPINC . '/pluggable.php';
wp_cookie_constants();
}
self::$user = \wp_get_current_user();
}
}
/**
* Returns the default role for the given user.
*
* @param int|\WP_User|null $user - The WP user.
*
* @since 2.2.0
*/
public static function get_user_role( $user = null ): string {
self::set_proper_user( $user );
if ( 0 === self::$user->ID || \is_bool( self::$user ) ) {
return '';
}
if ( \is_multisite() ) {
$blog_id = \get_current_blog_id();
if ( ! is_user_member_of_blog( self::$user->ID, $blog_id ) ) {
$user_blog_id = \get_active_blog_for_user( self::$user->ID );
if ( null !== $user_blog_id ) {
self::$user = new \WP_User(
// $user_id
self::$user->ID,
// $name | login, ignored if $user_id is set
'',
// $blog_id
$user_blog_id->blog_id
);
}
}
}
$role = reset( self::$user->roles );
/*
* The code looks like this for clearness only
*/
if ( \is_multisite() ) {
/*
* On multi site we can have user which has no assigned role, but it is superadmin.
* If the check confirms that - assign the role of the administrator to the user in order not to break our code.
*
* Unfortunately we could never be sure what is the name of the administrator role (someone could change this default value),
* in order to continue working we will use the presumption that if given role has 'manage_options' capability, then it is
* most probably administrator - so we will assign that role to the user.
*/
if ( false === $role && is_super_admin( self::$user->ID ) ) {
$wp_roles = WP_Helper::get_roles_wp();
foreach ( $wp_roles as $role_name => $wp_role ) {
$admin_role_set = get_role( $role_name )->capabilities;
if ( $admin_role_set['manage_options'] ) {
$role = $role_name;
break;
}
}
}
}
return (string) $role;
}
/**
* Returns the default blog_id for the given user.
*
* @param int|\WP_User|null $user - The WP user.
*
* @since 2.5.0
*/
public static function get_user_default_blog( $user = null ): int {
self::set_proper_user( $user );
if ( 0 === self::$user->ID ) {
return 0;
}
if ( \is_multisite() ) {
$blog_id = \get_current_blog_id();
if ( ! is_user_member_of_blog( self::$user->ID, $blog_id ) ) {
$blog_id = \get_active_blog_for_user( self::$user->ID );
if ( $blog_id instanceof \WP_Site ) {
return (int) $blog_id->blog_id;
} else {
return 1;
}
}
} else {
return 1;
}
return (int) $blog_id;
}
/**
* Checks if the user method is within the selected methods for the given role.
*
* @param int|\WP_User|null $user - The WP user.
*
* @since 2.2.0
*/
public static function is_user_method_in_role_enabled_methods( $user = null ): bool {
$enabled_method = self::get_enabled_method_for_user( $user );
if ( empty( $enabled_method ) ) {
return false;
}
$is_method_available = Settings::is_provider_enabled_for_role( self::get_user_role( $user ), $enabled_method );
return $is_method_available;
}
/**
* Removes all the meta keys associated with the given user.
*
* @param int|\WP_User|null $user - The WP user for which we have to remove the meta data.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_all_2fa_meta_for_user( $user = null ) {
self::set_proper_user( $user );
$user_meta_values = array_filter(
\get_user_meta( self::$user->ID ),
function ( $key ) {
return 0 === strpos( $key, WP_2FA_PREFIX );
},
ARRAY_FILTER_USE_KEY
);
foreach ( array_keys( $user_meta_values ) as $meta_name ) {
self::remove_meta( $meta_name, $user );
}
if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
if ( Extensions_Loader::use_proxytron() ) {
$user_blog_id = 1;
if ( WP_Helper::is_multisite() ) {
$user_blog_id = \get_active_blog_for_user( self::$user->ID )->blog_id;
}
if ( ( $current_blog = \get_current_blog_id() ) !== $user_blog_id ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found, Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
if ( WP_Helper::is_multisite() ) {
wp2fa_freemius()->switch_to_blog( $user_blog_id );
}
User_Licensing::method_has_been_set();
if ( WP_Helper::is_multisite() ) {
wp2fa_freemius()->switch_to_blog( $current_blog );
}
}
}
}
/*
* Fires when the user method is removed.
*
* @param \WP_User $user - The user for which the method has been removed.
*
* @since 2.2.2
*/
\do_action( WP_2FA_PREFIX . 'method_has_been_removed', self::get_user() );
}
/**
* Quick boolean check for whether a given user is using two-step.
*
* @since 2.2.0
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return bool
*/
public static function is_user_using_two_factor( $user = null ) {
self::set_proper_user( $user );
return ! empty( self::get_enabled_method_for_user() );
}
/**
* Gets the user grace period from meta.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return mixed
*
* @since 2.2.0
*/
public static function get_grace_period( $user = null ) {
self::set_proper_user( $user );
return self::get_meta( self::USER_GRACE_KEY, self::$user );
}
/**
* Sets the user grace period from meta.
*
* @param string $value - The value of the meta key.
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return mixed
*
* @since 2.2.0
*/
public static function set_grace_period( $value, $user = null ) {
self::set_proper_user( $user );
return self::set_meta( self::USER_GRACE_KEY, $value, self::$user );
}
/**
* Sets the user grace period from meta.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return void
*
* @since 2.2.0
*/
public static function remove_grace_period( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_GRACE_KEY, $user );
}
/**
* Checks if the user is locked. It only checks a single user meta field to keep this as fast as possible. The
* value of the field is updated elsewhere.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return bool True if the user account is locked. False otherwise.
*
* @since 2.2.0
*/
public static function is_user_locked( $user = null ): bool {
return (bool) self::get_grace_period( $user );
}
/**
* Checks if the given user has administrator or super administrator privileges.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @since 2.2.0
*/
public static function is_admin( $user = null ): bool {
self::set_proper_user( $user );
$is_admin = in_array( 'administrator', self::$user->roles, true ) || ( function_exists( 'is_super_admin' ) && is_super_admin( self::$user->ID ) );
if ( ! $is_admin ) {
return false;
}
return true;
}
/**
* Checks if user is excluded.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function is_excluded( $user = null ) {
$state = self::get_user_state( $user );
if ( 'excluded' !== $state ) {
$user_role = self::get_user_role( $user );
if ( Settings_Utils::string_to_bool( WP2FA::get_wp2fa_setting( 'superadmins-role-exclude' ) ) && is_super_admin( self::$user->ID ) ) {
$state = 'excluded';
self::set_user_state( $state, $user );
self::remove_enabled_method_for_user( $user );
}
// User does not have role assigned, exclude them.
if ( '' === $user_role ) {
$state = 'excluded';
self::set_user_state( $state, $user );
}
}
return 'excluded' === $state;
}
/**
* Updates the user state based on the current plugin settings.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return string
*
* @since 2.3
*/
public static function update_user_state( $user = null ) {
self::set_proper_user( $user );
$enforcement_state = 'optional';
if ( self::run_user_exclusion_check( self::get_user() ) ) {
$enforcement_state = 'excluded';
} elseif ( self::run_user_enforcement_check( self::get_user() ) ) {
$enforcement_state = 'enforced';
}
self::set_user_state( $enforcement_state );
// Clear enabled methods if excluded.
if ( 'excluded' === $enforcement_state ) {
self::remove_enabled_method_for_user();
}
return $enforcement_state;
}
/**
* Checks if user is enforced.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.2.0
*/
public static function is_enforced( $user = null ) {
$state = self::get_user_state( $user );
return 'enforced' === $state;
}
/**
* Returns the current user state stored.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return string
*
* @since 2.2.0
*/
public static function get_user_state( $user = null ) {
self::set_proper_user( $user );
$state = self::get_meta( self::USER_ENFORCEMENT_STATE );
if ( empty( $state ) ) {
$state = self::update_user_state();
}
return $state;
}
/**
* Returns the current user state stored.
*
* @param string $state - The 2FA user state.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
public static function set_user_state( $state, $user = null ) {
self::set_proper_user( $user );
if ( ! in_array( $state, self::USER_STATE_STATUSES, true ) ) {
$state = self::USER_STATE_STATUSES[0];
}
self::set_meta( self::USER_ENFORCEMENT_STATE, $state );
}
/**
* Removes 2FA meta for the given user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.2
*/
public static function remove_2fa_for_user( $user = null ) {
self::set_proper_user( $user );
self::remove_all_2fa_meta_for_user( $user );
}
/**
* Figures out the correct 2FA status of a user and stores it against the user in DB. The method is static
* because it is temporarily used in user listing to update user accounts created prior to version 1.7.0.
*
* @param \WP_User $user - The user which status should be set.
*
* @return string
*
* @see \WP2FA\Admin\User_Listing
* @since 1.7.0
*/
public static function set_user_status( \WP_User $user ) {
$status = User_Utils::determine_user_2fa_status( $user );
$status_data = User_Utils::extract_statuses( $status );
if ( ! empty( $status_data ) ) {
self::set_2fa_status( $status_data['id'], $user );
return $status_data['label'];
}
return '';
}
/**
* Send email to setup authentication.
*
* @param [type] $user_id - The ID of the user.
*
* @return bool
*/
public static function send_expired_grace_email( $user_id ) {
// Bail if the user has not enabled this email.
if ( 'enable_account_locked_email' !== WP2FA::get_wp2fa_email_templates( 'send_account_locked_email' ) ) {
return false;
}
// Grab user data.
$user = get_userdata( $user_id );
// Grab user email.
$email = $user->user_email;
$subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_locked_email_subject' ), $user_id ) );
$message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_locked_email_body' ), $user_id ) );
return Settings_Page::send_email( $email, $subject, $message );
}
/**
* Checks if user needs to reconfigure the method
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return boolean
*/
public static function needs_to_reconfigure_method( $user = null ): bool {
self::set_proper_user( $user );
return ( ! empty( self::get_user_needs_to_reconfigure_2fa( self::get_user() ) ) && ! self::get_nag_status() && empty( self::get_enabled_method_for_user( self::get_user() ) ) );
}
/**
* Locks the user account if the grace period setting is configured and the user is currently out of their grace
* period. It also takes care of sending the "account locked" email to the user if not already sent before.
*
* @return bool True if the user account is locked. False otherwise.
*/
private static function lock_user_account_if_needed() {
$settings = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME );
if ( ! is_array( $settings ) || ( isset( $settings['enforcement-policy'] ) && 'do-not-enforce' === $settings['enforcement-policy'] ) ) {
// 2FA is not enforced, make sure to clear any related user meta previously created
self::remove_meta( WP_2FA_PREFIX . 'is_locked' );
self::remove_user_expiry_date();
self::remove_meta( WP_2FA_PREFIX . 'locked_account_notification' );
return false;
}
if ( self::is_excluded() ) {
return false;
}
$is_user_instantly_enforced = self::get_user_enforced_instantly();
if ( $is_user_instantly_enforced ) {
// no need to lock the account if the user is enforced to set 2FA up instantly.
return false;
}
// Do not lock if user has 2FA configured.
$has_enabled_method = self::get_2fa_status();
if ( 'has_enabled_methods' === $has_enabled_method ) {
return false;
}
$grace_period_expiry_time = self::get_user_expiry_date();
$grace_period_expired = ( ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < time() );
if ( $grace_period_expired ) {
/**
* Filter can be used to prevent locking of the user account when the grace period expires.
*
* @param boolean $should_be_locked Should account be locked? True by default.
* @param \WP_User $user WP_User object.
*
* @return boolean True if the user account should be locked.
* @since 2.0.0
*/
$should_be_locked = apply_filters( WP_2FA_PREFIX . 'should_account_be_locked_on_grace_period_expiration', true, self::get_user() );
if ( ! $should_be_locked ) {
return false;
}
// set "grace period expired" flag.
self::set_grace_period( true );
/**
* Allow 3rd party developers to execute additional code when grace period expires (account is locked)
*
* @param \WP_User $user WP_User object.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'after_grace_period_expired', self::get_user() );
/**
* Filter can be used to disable the email notification about locked user account.
*
* @param boolean $can_send Can the email notification be sent? True by default.
* @param \WP_User $user WP_User object.
*
* @return boolean True if the email notification can be sent.
* @since 2.0.0
*/
$notify_user = apply_filters( WP_2FA_PREFIX . 'send_account_locked_notification', true, self::get_user() );
if ( $notify_user ) {
// Send the email to alert the user, only if we have not done so before.
$account_notification = get_user_meta( self::get_user()->ID, WP_2FA_PREFIX . 'locked_account_notification', true );
if ( ! $account_notification ) {
self::send_expired_grace_email( self::get_user()->ID );
self::set_meta( WP_2FA_PREFIX . 'locked_account_notification', true );
}
}
// Grab user session and kill it, preferably with fire.
$manager = \WP_Session_Tokens::get_instance( self::get_user()->ID );
$manager->destroy_all();
return true;
}
return false;
}
/**
* Caches and returns the globally set excluded roles
*
* @return array
*
* @since 2.5.0
*/
private static function get_excluded_roles() {
if ( null === self::$excluded_roles ) {
self::$excluded_roles = WP2FA::get_wp2fa_setting( 'excluded_roles' );
}
return self::$excluded_roles;
}
/**
* Caches and returns the globally set enforced users
*
* @return array
*
* @since 2.5.0
*/
private static function get_enforced_users() {
if ( null === self::$enforced_users ) {
self::$enforced_users = WP2FA::get_wp2fa_setting( 'enforced_users' );
}
return self::$enforced_users;
}
/**
* Caches and returns the globally set excluded sites
*
* @return array
*
* @since 2.5.0
*/
private static function get_excluded_sites() {
if ( null === self::$excluded_sites ) {
self::$excluded_sites = WP2FA::get_wp2fa_setting( 'excluded_sites' );
}
return self::$excluded_sites;
}
/**
* Caches and returns the globally set excluded users
*
* @return array
*
* @since 2.5.0
*/
private static function get_excluded_users() {
if ( null === self::$excluded_users ) {
self::$excluded_users = WP2FA::get_wp2fa_setting( 'excluded_users' );
}
return self::$excluded_users;
}
/**
* Caches and returns the globally set included sites
*
* @return array
*
* @since 2.5.0
*/
private static function get_included_sites() {
if ( null === self::$included_sites ) {
self::$included_sites = WP2FA::get_wp2fa_setting( 'included_sites' );
}
return self::$included_sites;
}
/**
* Caches and returns the globally set enforced roles
*
* @return array
*
* @since 2.5.0
*/
private static function get_enforced_roles() {
if ( null === self::$enforced_roles ) {
self::$enforced_roles = WP2FA::get_wp2fa_setting( 'enforced_roles' );
}
return self::$enforced_roles;
}
/**
* Runs the necessary checks to figure out if the user is excluded based on current plugin settings.
*
* @param \WP_User $user User to evaluate.
* @param array $roles - Array with user roles.
* @param string $user_login - User login name.
* @param int $user_id - The id of the user.
*
* @return bool True if the user is excluded based on current plugin settings.
* @since 2.0.0
*
* @since 2.5.0 added params $roles, $user_login, $user_id . $user is with highest priority
*/
public static function run_user_exclusion_check( $user = null, $roles = null, $user_login = null, $user_id = null ) {
if ( null !== $user ) {
$user_roles = $user->roles;
$user_login = $user->user_login;
$user_id = $user->ID;
} else {
/**
* Setting that inner class flag because if we are here that means reports are generated, and we dont need to update users meta but just to check what is currently there.
*/
self::$update_started = true;
$user_roles = $roles;
}
$user_excluded = false;
$excluded_users = self::get_excluded_users();
if ( ! empty( $excluded_users ) ) {
// Compare our roles with the users and see if we get a match.
$result = in_array( $user_login, $excluded_users, true );
if ( $result ) {
return true;
}
}
$excluded_roles = self::get_excluded_roles();
if ( ! empty( $excluded_roles ) ) {
$excluded_roles = array_map( 'strtolower', $excluded_roles );
// Compare our roles with the users and see if we get a match.
$result = array_intersect( $excluded_roles, $user_roles );
if ( ! empty( $result ) ) {
return true;
}
}
if ( WP_Helper::is_multisite() ) {
$excluded_sites = self::get_excluded_sites();
if ( ! empty( $excluded_sites ) && is_array( $excluded_sites ) ) {
foreach ( $excluded_sites as $site_id ) {
if ( is_user_member_of_blog( $user_id, $site_id ) ) {
// User is a member of the blog we are excluding from 2FA.
return true;
} else {
// User is NOT a member of the blog we are excluding.
$user_excluded = false;
}
}
}
$included_sites = self::get_included_sites();
if ( $included_sites && is_array( $included_sites ) ) {
foreach ( $included_sites as $site_id ) {
if ( is_user_member_of_blog( $user_id, $site_id ) ) {
$user_excluded = false;
}
}
}
}
return $user_excluded;
}
/**
* Runs the necessary checks to figure out if the user is enforced based on current plugin settings.
*
* @param \WP_User $user User to evaluate.
* @param array $roles - Array with user roles.
* @param string $user_login - User login name.
* @param int $user_id - The id of the user.
*
* @return bool True if the user is enforced based on current plugin settings.
*
* @since 2.0.0
*
* @since 2.5.0 added params $roles, $user_login, $user_id . $user is with highest priority
*/
public static function run_user_enforcement_check( $user = null, $roles = null, $user_login = null, $user_id = null ) {
if ( null !== $user ) {
$user_roles = $user->roles;
$user_login = $user->user_login;
$user_id = $user->ID;
} else {
/**
* Setting that inner class flag because if we are here that means reports are generated, and we dont need to update users meta but just to check what is currently there.
*/
self::$update_started = true;
$user_roles = $roles;
}
$current_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
$enabled_method = self::get_enabled_method_for_user( $user_id );
$user_eligible = false;
if ( Settings_Utils::string_to_bool( WP2FA::get_wp2fa_setting( 'superadmins-role-exclude' ) ) && is_super_admin( $user_id ) ) {
return false;
}
// Let's check the policy settings and if the user has setup totp/email by checking for the usermeta.
if ( empty( $enabled_method ) && WP_Helper::is_multisite() && 'superadmins-only' === $current_policy ) {
return is_super_admin( $user_id );
} elseif ( empty( $enabled_method ) && WP_Helper::is_multisite() && 'superadmins-siteadmins-only' === $current_policy ) {
return self::is_admin( $user_id );
} elseif ( 'all-users' === $current_policy && empty( $enabled_method ) ) {
$excluded_users = self::get_excluded_users();
if ( ! empty( $excluded_users ) ) {
// Compare our roles with the users and see if we get a match.
$result = in_array( $user_login, $excluded_users, true );
if ( $result ) {
return false;
}
$user_eligible = true;
}
$excluded_roles = self::get_excluded_roles();
if ( ! empty( $excluded_roles ) ) {
if ( ! WP_Helper::is_multisite() ) {
// Compare our roles with the users and see if we get a match.
$result = array_intersect( $excluded_roles, $user_roles );
if ( ! empty( $result ) ) {
return false;
}
} else {
$users_caps = array();
$subsites = get_sites();
// Check each site and add to our array so we know each users actual roles.
foreach ( $subsites as $subsite ) {
$subsite_id = get_object_vars( $subsite )['blog_id'];
global $wpdb;
if ( 1 === (int) $subsite_id ) {
$users_caps[] = get_user_meta( $user_id, $wpdb->base_prefix . 'capabilities', true );
} else {
$users_caps[] = get_user_meta( $user_id, $wpdb->base_prefix . $subsite_id . '_capabilities', true );
}
}
foreach ( $users_caps as $key => $value ) {
if ( ! empty( $value ) ) {
foreach ( $value as $key => $value ) {
$result = in_array( $key, $excluded_roles, true );
}
}
}
if ( ! empty( $result ) ) {
return false;
}
}
}
if ( true === $user_eligible || empty( $enabled_method ) ) {
return true;
}
} elseif ( ( 'certain-roles-only' === $current_policy || 'certain-users-only' === $current_policy ) && empty( $enabled_method ) ) {
$enforced_users = self::get_enforced_users();
if ( ! empty( $enforced_users ) ) {
// Compare our roles with the users and see if we get a match.
$result = in_array( $user_login, $enforced_users, true );
// The user is one of the chosen roles we are forcing 2FA onto, so lets show the nag.
if ( ! empty( $result ) ) {
return true;
}
}
$enforced_roles = self::get_enforced_roles();
if ( ! empty( $enforced_roles ) ) {
// Turn it into an array.
$enforced_roles_array = Settings_Page::extract_roles_from_input( $enforced_roles );
if ( ! WP_Helper::is_multisite() ) {
// Compare our roles with the users and see if we get a match.
$result = array_intersect( $enforced_roles_array, $user_roles );
// The user is one of the chosen roles we are forcing 2FA onto, so lets show the nag.
if ( ! empty( $result ) ) {
return true;
}
} else {
$users_caps = array();
$subsites = get_sites();
// Check each site and add to our array so we know each users actual roles.
foreach ( $subsites as $subsite ) {
$subsite_id = get_object_vars( $subsite )['blog_id'];
global $wpdb;
if ( 1 === (int) $subsite_id ) {
$users_caps[] = get_user_meta( $user_id, $wpdb->prefix . 'capabilities', true );
} else {
$users_caps[] = get_user_meta( $user_id, $wpdb->prefix . $subsite_id . '_capabilities', true );
}
}
foreach ( $users_caps as $role_in_site ) {
if ( ! empty( $role_in_site ) ) {
foreach ( array_keys( $role_in_site ) as $role ) {
if ( in_array( $role, $enforced_roles_array, true ) ) {
// User is enforced somewhere.
return true;
}
}
}
}
return false;
}
}
if ( Settings_Utils::string_to_bool( WP2FA::get_wp2fa_setting( 'superadmins-role-add' ) ) ) {
return is_super_admin( $user_id );
}
} elseif ( 'enforce-on-multisite' === $current_policy ) {
$included_sites = self::get_included_sites();
foreach ( $included_sites as $site_id ) {
if ( is_user_member_of_blog( $user_id, $site_id ) ) {
return true;
}
}
} elseif ( 'all-users' === $current_policy && ! empty( $enabled_method ) ) {
return true;
}
return false;
}
/**
* Runs the necessary checks to figure out if the user is enforced based on current plugin settings.
*
* @param \WP_User $user User to evaluate.
* @param array $roles - Array with user roles.
* @param string $user_login - User login name.
* @param int $user_id - The id of the user.
*
* @return bool True if the user is enforced based on current plugin settings.
*
* @since 2.0.0
*
* @since 2.5.0 added params $roles, $user_login, $user_id . $user is with highest priority
*/
public static function is_user_enforced( $user = null, $roles = null, $user_login = null, $user_id = null ) {
if ( null !== $user ) {
$user_roles = $user->roles;
$user_login = $user->user_login;
$user_id = $user->ID;
} else {
/**
* Setting that inner class flag because if we are here that means reports are generated, and we dont need to update users meta but just to check what is currently there.
*/
self::$update_started = true;
$user_roles = $roles;
}
$current_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
$user_eligible = false;
if ( Settings_Utils::string_to_bool( WP2FA::get_wp2fa_setting( 'superadmins-role-exclude' ) ) && is_super_admin( $user_id ) ) {
return false;
}
// Let's check the policy settings and if the user has setup totp/email by checking for the usermeta.
if ( WP_Helper::is_multisite() && 'superadmins-only' === $current_policy ) {
return is_super_admin( $user_id );
} elseif ( WP_Helper::is_multisite() && 'superadmins-siteadmins-only' === $current_policy ) {
return self::is_admin( $user_id );
} elseif ( 'all-users' === $current_policy ) {
$excluded_users = self::get_excluded_users();
if ( ! empty( $excluded_users ) ) {
// Compare our roles with the users and see if we get a match.
$result = in_array( $user_login, $excluded_users, true );
if ( $result ) {
return false;
}
$user_eligible = true;
}
$excluded_roles = self::get_excluded_roles();
if ( ! empty( $excluded_roles ) ) {
if ( ! WP_Helper::is_multisite() ) {
// Compare our roles with the users and see if we get a match.
$result = array_intersect( $excluded_roles, $user_roles );
if ( ! empty( $result ) ) {
return false;
}
} else {
$users_caps = array();
$subsites = get_sites();
// Check each site and add to our array so we know each users actual roles.
foreach ( $subsites as $subsite ) {
$subsite_id = get_object_vars( $subsite )['blog_id'];
global $wpdb;
if ( 1 === (int) $subsite_id ) {
$users_caps[] = get_user_meta( $user_id, $wpdb->base_prefix . 'capabilities', true );
} else {
$users_caps[] = get_user_meta( $user_id, $wpdb->base_prefix . $subsite_id . '_capabilities', true );
}
}
foreach ( $users_caps as $key => $value ) {
if ( ! empty( $value ) ) {
foreach ( $value as $key => $value ) {
$result = in_array( $key, $excluded_roles, true );
}
}
}
if ( ! empty( $result ) ) {
return false;
}
}
}
if ( true === $user_eligible ) {
return true;
}
} elseif ( ( 'certain-roles-only' === $current_policy || 'certain-users-only' === $current_policy ) ) {
$enforced_users = self::get_enforced_users();
if ( ! empty( $enforced_users ) ) {
// Compare our roles with the users and see if we get a match.
$result = in_array( $user_login, $enforced_users, true );
// The user is one of the chosen roles we are forcing 2FA onto, so lets show the nag.
if ( ! empty( $result ) ) {
return true;
}
}
$enforced_roles = self::get_enforced_roles();
if ( ! empty( $enforced_roles ) ) {
// Turn it into an array.
$enforced_roles_array = Settings_Page::extract_roles_from_input( $enforced_roles );
if ( ! WP_Helper::is_multisite() ) {
// Compare our roles with the users and see if we get a match.
$result = array_intersect( $enforced_roles_array, $user_roles );
// The user is one of the chosen roles we are forcing 2FA onto, so lets show the nag.
if ( ! empty( $result ) ) {
return true;
}
} else {
$users_caps = array();
$subsites = get_sites();
// Check each site and add to our array so we know each users actual roles.
foreach ( $subsites as $subsite ) {
$subsite_id = get_object_vars( $subsite )['blog_id'];
global $wpdb;
if ( 1 === (int) $subsite_id ) {
$users_caps[] = get_user_meta( $user_id, $wpdb->prefix . 'capabilities', true );
} else {
$users_caps[] = get_user_meta( $user_id, $wpdb->prefix . $subsite_id . '_capabilities', true );
}
}
foreach ( $users_caps as $key => $value ) {
if ( ! empty( $value ) ) {
foreach ( $value as $key => $value ) {
$result = in_array( $key, $enforced_roles_array, true );
}
}
}
if ( ! empty( $result ) ) {
return true;
}
}
}
if ( Settings_Utils::string_to_bool( WP2FA::get_wp2fa_setting( 'superadmins-role-add' ) ) ) {
return is_super_admin( $user_id );
}
} elseif ( 'enforce-on-multisite' === $current_policy ) {
$included_sites = self::get_included_sites();
foreach ( $included_sites as $site_id ) {
if ( is_user_member_of_blog( $user_id, $site_id ) ) {
return true;
}
}
} elseif ( 'all-users' === $current_policy ) {
return true;
}
return false;
}
/**
* Returns the nominated email for user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.6.0
*/
public static function get_nominated_email_for_user( $user = null ) {
self::set_proper_user( $user );
$email = self::get_meta( self::USER_NOMINATED_EMAIL );
if ( empty( $email ) || ! isset( $email ) ) {
$email = self::get_user()->user_email;
}
return $email;
}
/**
* Sets the nominated email for the user. If the email is the same as the current user email from the WP - the meta is not populated.
*
* @param string $email - The token to set for the user.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.6.0
*/
public static function set_nominated_email_for_user( string $email, $user = null ) {
self::set_proper_user( $user );
$email = \sanitize_email( \wp_unslash( $email ) );
if ( ! empty( $email ) ) {
if ( self::get_user()->user_email !== $email ) {
return self::set_meta( self::USER_NOMINATED_EMAIL, $email );
} else {
self::remove_nominated_email_for_user( $user );
}
}
return false;
}
/**
* Removes the nominated email for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.6.0
*/
public static function remove_nominated_email_for_user( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_NOMINATED_EMAIL, self::$user );
}
/**
* Returns the backup email for user. If the data stored in meta is = 'wp_mail' that means that the user email should be extracted from the WP BE.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.6.0
*/
public static function get_backup_email_for_user( $user = null ) {
self::set_proper_user( $user );
$email = self::get_meta( self::USER_BACKUP_EMAIL );
if ( empty( $email ) || ! isset( $email ) ) {
$email = self::get_user()->user_email;
}
if ( isset( $email ) && 'wp_mail' === $email ) {
$email = self::get_user()->user_email;
}
return $email;
}
/**
* Sets the backup email for the user. If the email is the same as the current user email from the WP - the meta is not populated.
*
* @param string $email - The token to set for the user.
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return mixed
*
* @since 2.6.0
*/
public static function set_backup_email_for_user( string $email, $user = null ) {
self::set_proper_user( $user );
$email = \sanitize_email( \wp_unslash( $email ) );
if ( ! empty( $email ) ) {
if ( self::get_user()->user_email !== $email ) {
return self::set_meta( self::USER_BACKUP_EMAIL, $email );
} elseif ( self::get_user()->user_email === $email ) {
return self::set_meta( self::USER_BACKUP_EMAIL, 'wp_mail' );
} else {
self::remove_backup_email_for_user( $user );
}
}
return false;
}
/**
* Removes the backup email for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.6.0
*/
public static function remove_backup_email_for_user( $user = null ) {
self::set_proper_user( $user );
self::remove_meta( self::USER_BACKUP_EMAIL, self::$user );
}
/**
* Updates teh user metadata. Checks for changes in the global settings, and if it finds some, checks these against the given user metadata settings hash and updates the user metadata if necessary.
*
* @return void
*
* @since 2.4.1
*/
private static function update_meta_if_necessary() {
$global_settings_hash = Settings_Utils::get_option( WP_2FA_PREFIX . 'settings_hash' );
if ( ! empty( $global_settings_hash ) ) {
$stored_hash = self::get_global_settings_hash_for_user( self::get_user() );
if ( $global_settings_hash !== $stored_hash ) {
self::set_global_settings_hash_for_user( $global_settings_hash, self::get_user() );
// update necessary user attributes (user meta) based on changed settings; the enforcement check
// needs to run first as function "set_user_policies_and_grace" relies on having the correct values.
self::check_methods_and_set_user();
self::update_user_state( self::get_user() );
self::set_user_policies_and_grace();
self::remove_backup_methods( self::get_user() );
}
self::lock_user_account_if_needed();
}
}
/**
* Sets the proper user policies and grace.
*
* @return void
*
* @since 2.4.1
*/
private static function set_user_policies_and_grace() {
$enabled_methods_for_the_user = self::get_enabled_method_for_user( self::get_user() );
if ( ! empty( $enabled_methods_for_the_user ) ) {
self::remove_user_enforced_instantly( self::get_user() );
self::remove_user_expiry_date( self::get_user() );
self::remove_user_needs_to_reconfigure_2fa( self::get_user() );
self::set_user_status( self::get_user() );
return;
}
if ( self::is_enforced( self::get_user()->ID ) ) {
$grace_policy = Settings::get_role_or_default_setting( 'grace-policy', self::get_user() );
// Check if want to apply the custom period, or instant expiry.
if ( 'use-grace-period' === $grace_policy ) {
$custom_grace_period_duration =
Settings::get_role_or_default_setting( 'grace-period', self::get_user() ) . ' ' . Settings::get_role_or_default_setting( 'grace-period-denominator', self::get_user() );
$grace_expiry = strtotime( $custom_grace_period_duration );
self::remove_user_enforced_instantly( self::get_user() );
} else {
$grace_expiry = time();
}
self::set_user_expiry_date( (string) $grace_expiry, self::get_user() );
if ( 'no-grace-period' === $grace_policy ) {
self::set_user_enforced_instantly( true, self::get_user() );
}
} else {
self::remove_user_enforced_instantly( self::get_user() );
self::remove_user_expiry_date( self::get_user() );
self::remove_user_needs_to_reconfigure_2fa( self::get_user() );
}
// update the 2FA status meta field.
self::set_user_status( self::get_user() );
}
/**
* Checks the user methods and sets the user status.
*
* @return void
*
* @since 2.4.1
*/
private static function check_methods_and_set_user() {
if ( ! self::get_user_needs_to_reconfigure_2fa( self::get_user() ) ) {
$enabled_methods_for_the_user = self::get_enabled_method_for_user( self::get_user() );
if ( empty( $enabled_methods_for_the_user ) ) {
return;
}
$global_methods = Methods::get_available_2fa_methods();
if ( empty( \array_intersect( array( $enabled_methods_for_the_user ), $global_methods ) ) ) {
self::remove_enabled_method_for_user( self::get_user() );
if ( self::is_enforced( self::get_user()->ID ) ) {
self::set_user_needs_to_reconfigure_2fa( true, self::get_user() );
}
}
}
}
/**
* Calls all the backup methods and gives them and option to remove their stored values.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.5.0
*/
private static function remove_backup_methods( $user = null ) {
self::set_proper_user( $user );
\do_action( WP_2FA_PREFIX . 'remove_backup_methods_for_user', self::get_user() );
}
/**
* Sets the local variable class based on the given parameter.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return void
*
* @since 2.2.0
*/
private static function set_proper_user( $user = null ) {
if ( null !== $user ) {
self::set_user( $user );
} else {
self::get_user();
}
if ( false !== self::$user && 0 !== self::$user->ID && false === self::$update_started ) {
self::$update_started = true;
self::update_meta_if_necessary();
}
}
}
}
includes/classes/Admin/class-user-notices.php 0000644 00000023710 15075513060 0015300 0 ustar 00 <?php
/**
* Responsible for WP2FA user's notifying.
*
* @package wp2fa
* @subpackage user-utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin;
use WP2FA\WP2FA;
use WP2FA\Extensions_Loader;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Freemius\User_Licensing;
use WP2FA\Admin\Controllers\Methods;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Views\Grace_Period_Notifications;
use WP2FA\Extensions\WhiteLabeling\White_Labeling_Render;
/**
* User_Notices class with user notification filters
*
* @since 2.4.0
*/
if ( ! class_exists( '\WP2FA\Admin\User_Notices' ) ) {
/**
* User_Notices - Class for displaying notices to our users.
*/
class User_Notices {
/**
* Lets set things up
*/
public static function init() {
$enforcement_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
if ( ! empty( $enforcement_policy ) ) {
// Check we are supposed to, before adding action to show nag.
if ( in_array( $enforcement_policy, array( 'all-users', 'certain-roles-only', 'certain-users-only', 'superadmins-only', 'superadmins-siteadmins-only', 'enforce-on-multisite', true ), true ) ) {
$global_methods = Methods::get_available_2fa_methods();
$user = User_Helper::get_user_object();
$users_method = User_Helper::get_enabled_method_for_user( User_Helper::get_user_object() );
if ( Grace_Period_Notifications::notify_using_dashboard( User_Helper::get_user_object() ) ) {
add_action( 'admin_notices', array( __CLASS__, 'user_setup_2fa_nag' ) );
add_action( 'network_admin_notices', array( __CLASS__, 'user_setup_2fa_nag' ) );
}
// If enaabled method is no longer available, show nag so users reconfigures using an available remaining method.
if ( User_Helper::is_enforced( $user ) && ! empty( $users_method ) && empty( \array_intersect( array( $users_method ), $global_methods ) ) ) {
}
} elseif ( 'do-not-enforce' === WP2FA::get_wp2fa_setting( 'enforcement-policy' ) ) {
add_action( 'admin_notices', array( __CLASS__, 'user_reconfigure_2fa_nag' ) );
add_action( 'network_admin_notices', array( __CLASS__, 'user_setup_2fa_nag' ) );
}
}
}
/**
* The nag content
*
* @param string $is_shortcode - Is that a call from shortcode.
* @param string $configure_2fa_url - The configuration url.
*
* @return void
*/
public static function user_setup_2fa_nag( $is_shortcode = '', $configure_2fa_url = '' ) {
if ( isset( $_GET['user_id'] ) ) { // phpcs:ignore
$current_profile_user_id = (int) $_GET['user_id']; // phpcs:ignore
} elseif ( ! is_null( User_Helper::get_user_object() ) ) {
$current_profile_user_id = User_Helper::get_user_object()->ID;
} else {
$current_profile_user_id = false;
}
if ( ! $current_profile_user_id ||
isset( $_GET['user_id'] ) && // phpcs:ignore
$_GET['user_id'] !== User_Helper::get_user_object()->ID || // phpcs:ignore
User_Helper::get_user_enforced_instantly( User_Helper::get_user_object() ) ) {
return;
}
$grace_expiry = (int) User_Helper::get_user_expiry_date( User_Helper::get_user_object() );
$class = 'notice notice-info wp-2fa-nag';
if ( User_Helper::get_user_needs_to_reconfigure_2fa( User_Helper::get_user_object() ) ) {
$message = WP2FA::get_wp2fa_white_label_setting( 'default-2fa-resetup-required-notice', true );
} else {
$message = WP2FA::get_wp2fa_white_label_setting( 'default-2fa-required-notice', true );
}
$is_nag_dismissed = User_Helper::get_nag_status();
$is_nag_needed = User_Helper::is_enforced( User_Helper::get_user_object()->ID );
$is_user_excluded = User_Helper::is_excluded( User_Helper::get_user_object()->ID );
$enabled_methods = User_Helper::get_enabled_method_for_user( User_Helper::get_user_object() );
$new_page_id = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
if ( empty( $new_page_id ) ) {
$new_page_id = Settings::get_custom_settings_page_id( '', User_Helper::get_user_object() );
}
$new_page_permalink = get_permalink( $new_page_id );
$setup_url = Settings::get_setup_page_link();
// Allow setup URL to be customized if outputting via shortcode.
if ( isset( $is_shortcode ) && 'output_shortcode' === $is_shortcode && ! empty( $configure_2fa_url ) ) {
$setup_url = $configure_2fa_url;
}
// Stop the page from being a link to a page this user cant access if needed.
if ( WP_Helper::is_multisite() && ! is_user_member_of_blog( User_Helper::get_user_object()->ID ) ) {
$new_page_id = false;
}
// If we have a custom page generated, lets use it.
if ( ! empty( $new_page_id ) && $new_page_permalink ) {
$setup_url = $new_page_permalink;
}
// If the nag has not already been dismissed, and of course if the user is eligible, lets show them something.
if ( ! $is_nag_dismissed && $is_nag_needed && empty( $enabled_methods ) && ! $is_user_excluded && ! empty( $grace_expiry ) ) {
$show = true;
if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
if ( Extensions_Loader::use_proxytron() ) {
$show = User_Licensing::enable_2fa_user_setting( true );
}
}
if ( $show ) {
echo '<div class="' . \esc_attr( $class ) . '">';
echo wpautop( \wp_kses_post( WP2FA::replace_remaining_grace_period( $message, (int) $grace_expiry ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo ' <a href="' . \esc_url( $setup_url ) . '" class="button button-primary">' . \esc_html__( 'Configure 2FA now', 'wp-2fa' ) . '</a>';
echo ' <a href="#" class="button button-secondary dismiss-user-configure-nag">' . \esc_html__( 'Remind me on next login', 'wp-2fa' ) . '</a></p>';
echo '</div>';
}
} else {
self::user_reconfigure_2fa_nag();
}
}
/**
* The nag content
*/
public static function user_reconfigure_2fa_nag() {
// If the nag has not already been dismissed, and of course if the user is eligible, lets show them something.
if ( User_Helper::needs_to_reconfigure_method() ) {
$class = 'notice notice-info wp-2fa-nag';
$message = \esc_html__( 'The 2FA method you were using is no longer allowed on this website. Please reconfigure 2FA using one of the supported methods.', 'wp-2fa' );
echo '<div class="' . \esc_attr( $class ) . '"><p>' . \esc_html( $message );
echo ' <a href="' . \esc_url( Settings::get_setup_page_link() ) . '" class="button button-primary">' . \esc_html__( 'Configure 2FA now', 'wp-2fa' ) . '</a>';
echo ' <a href="#" class="button button-secondary wp-2fa-button-secondary dismiss-user-reconfigure-nag">' . \esc_html__( 'I\'ll do it later', 'wp-2fa' ) . '</a></p>';
echo '</div>';
}
}
/**
* Dismiss notice and setup a user meta value so we know its been dismissed
*/
public static function dismiss_nag() {
User_Helper::set_nag_status( true );
}
/**
* Reset the nag when the user logs out, so they get it again next time.
*
* @param [type] $user_id - The ID of the user.
*
* @return void
*/
public static function reset_nag( $user_id ) {
User_Helper::remove_nag_status( $user_id );
}
/**
* Adds setting option in the white label settings page.
*
* @return void
*
* @since 2.5.0
*/
public static function white_label_settings_text() {
?>
<tr>
<th><label for="email-backup-method"><?php \esc_html_e( '2FA mandatory notice', 'wp-2fa' ); ?></label></th>
<td>
<?php
echo White_Labeling_Render::get_method_text_editor( 'default-2fa-required-notice' ); // phpcs:ignore
?>
<div style="margin-top: 5px;"><span><strong><i><?php \esc_html_e( 'Note:', 'wp-2fa' ); ?></i></strong> <?php \esc_html_e( 'Only plain text is allowed.', 'wp-2fa' ); ?></span></div>
</td>
</tr>
<tr>
<th><label for="email-backup-method"><?php \esc_html_e( '2FA reconfiguration mandatory notice', 'wp-2fa' ); ?></label></th>
<td>
<?php
echo White_Labeling_Render::get_method_text_editor( 'default-2fa-resetup-required-notice' ); // phpcs:ignore
?>
<div style="margin-top: 5px;"><span><strong><i><?php \esc_html_e( 'Note:', 'wp-2fa' ); ?></i></strong> <?php \esc_html_e( 'Only plain text is allowed.', 'wp-2fa' ); ?></span></div>
</td>
</tr>
<tr>
<th><label><?php \esc_html_e( 'User profile 2FA configuration area title', 'wp-2fa' ); ?></label></th>
<td>
<?php
echo White_Labeling_Render::get_method_text_editor( 'user-profile-form-preamble-title' ); // phpcs:ignore
?>
<div style="margin-top: 5px;"><span><strong><i><?php \esc_html_e( 'Note:', 'wp-2fa' ); ?></i></strong> <?php \esc_html_e( 'Only plain text is allowed.', 'wp-2fa' ); ?></span></div>
</td>
</tr>
<tr>
<th><label><?php \esc_html_e( 'User profile 2FA configuration area description', 'wp-2fa' ); ?></label></th>
<td>
<?php
echo White_Labeling_Render::get_method_text_editor( 'user-profile-form-preamble-desc' ); // phpcs:ignore
?>
<div style="margin-top: 5px;"><span><strong><i><?php \esc_html_e( 'Note:', 'wp-2fa' ); ?></i></strong> <?php \esc_html_e( 'Only plain text is allowed.', 'wp-2fa' ); ?></span></div>
</td>
</tr>
<?php
// phpcs:disable
// phpcs:enable
?>
<?php
}
/**
* Adds and filters extension values in the settings store array ($output).
*
* @param array $output - Array with the currently stored settings.
* @param array $input - Array with the input ($_POST) values.
*
* @return array
*
* @since 2.5.0
*/
public static function settings_store( array $output, array $input ) {
if ( isset( $input['default-2fa-required-notice'] ) ) {
$output['default-2fa-required-notice'] = \wp_kses_post( $input['default-2fa-required-notice'] );
}
if ( isset( $input['default-2fa-resetup-required-notice'] ) ) {
$output['default-2fa-resetup-required-notice'] = \wp_kses_post( $input['default-2fa-resetup-required-notice'] );
}
return $output;
}
}
}
includes/classes/Admin/SettingsPages/class-settings-page-render.php 0000644 00000014633 15075513060 0021473 0 ustar 00 <?php
/**
* Settings page render class.
*
* @package wp2fa
* @subpackage views
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\SettingsPages;
use WP2FA\WP2FA;
use WP2FA\Admin\Helpers\WP_Helper;
if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Settings_Page_Render' ) ) {
/**
* Settings_Page_Render - Class for rendering the plugin settings settings
*
* @since 2.0.0
*/
class Settings_Page_Render {
/**
* Render the settings
*/
public static function render() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$main_user = get_current_user_id();
$current_user_id = $main_user;
if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
$main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
}
?>
<div class="wrap wp-2fa-settings-wrapper wp2fa-form-styles">
<h2><?php \esc_html_e( 'WP 2FA Settings', 'wp-2fa' ); ?></h2>
<hr>
<br>
<?php if ( ! empty( WP2FA::get_wp2fa_general_setting( 'limit_access' ) ) && $main_user !== $current_user_id ) { ?>
<?php
echo \esc_html__( 'These settings have been disabled by your site administrator, please contact them for further assistance.', 'wp-2fa' );
?>
<?php } else { ?>
<?php
/**
* Fires before the plugin settings rendering.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'before_plugin_settings' );
?>
<div class="nav-tab-wrapper">
<?php
$settings = self::settings_array();
/**
* Stores the default settings key, so there is no need to walk the entire array again to extract that value
*/
$default_settings_key = 'generic-settings';
foreach ( $settings as $setting_tab => $setting_values ) {
$active_class = '';
if ( ! isset( $_REQUEST['tab'] ) && $setting_values['default'] ) { // phpcs:ignore
$active_class = 'nav-tab-active';
$default_settings_key = $setting_tab;
} elseif ( isset( $_REQUEST['tab'] ) && $setting_tab === $_REQUEST['tab'] ) { // phpcs:ignore
$active_class = 'nav-tab-active';
}
echo '<a href="' . $setting_values['url'] . '" class="nav-tab ' . $active_class . '">' . $setting_values['name'] . '</a>'; // phpcs:ignore
}
?>
</div>
<?php
$show_tab = $default_settings_key;
if ( isset( $_REQUEST['tab'] ) && array_key_exists( $_REQUEST['tab'], $settings ) ) { // phpcs:ignore
$show_tab = \sanitize_text_field( \wp_unslash( $_REQUEST['tab'] ) ); // phpcs:ignore
}
if ( WP_Helper::is_multisite() ) {
$action = 'edit.php?action=' . $settings[ $show_tab ]['network_action'];
} else {
$action = 'options.php';
}
?>
<br/>
<?php
$settings[ $show_tab ]['description'];
?>
<br/>
<form id="wp-2fa-admin-settings" action='<?php echo \esc_attr( $action ); ?>' method='post' autocomplete="off" >
<?php
\call_user_func( array( $settings[ $show_tab ]['class'], $settings[ $show_tab ]['method'] ) );
?>
</form>
<?php } ?>
</div>
<?php
}
/**
* Holds the array with all the settings of the plugin. Fires filter, so third parties could change these settings.
*
* @return array
*
* @since 2.2.0
*/
private static function settings_array(): array {
$email_settings_name = \esc_html__( 'Emails & templates', 'wp-2fa' );
$settings_tabs = array(
'generic-settings' => array(
'url' => \esc_url(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'tab' => 'generic-settings',
),
network_admin_url( 'admin.php' )
)
),
'name' => \esc_html__( 'General settings', 'wp-2fa' ),
'default' => true,
'description' => sprintf(
'<p class="description">%1$s <a href="mailto:support@melapress.com">%2$s</a></p>',
\esc_html__( 'Use the settings below to configure the properties of the two-factor authentication on your website and how users use it. If you have any questions send us an email at', 'wp-2fa' ),
\esc_html__( 'support@melapress.com', 'wp-2fa' )
),
'class' => 'WP2FA\Admin\SettingsPages\Settings_Page_General',
'method' => 'render',
'network_action' => 'update_wp2fa_network_options',
),
'email-settings' => array(
'url' => \esc_url(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'tab' => 'email-settings',
),
network_admin_url( 'admin.php' )
)
),
'name' => $email_settings_name,
'default' => false,
'description' => sprintf(
'<p class="description">%1$s <a href="mailto:support@melapress.com">%2$s</a></p>',
\esc_html__( 'Use the settings below to configure the properties of the two-factor authentication on your website and how users use it. If you have any questions send us an email at', 'wp-2fa' ),
\esc_html__( 'support@melapress.com', 'wp-2fa' )
),
'class' => 'WP2FA\Admin\SettingsPages\Settings_Page_Email',
'method' => 'render',
'network_action' => 'update_wp2fa_network_email_options',
),
'white-label-settings' => array(
'url' => \esc_url(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'tab' => 'white-label-settings',
),
network_admin_url( 'admin.php' )
)
),
'name' => \esc_html__( 'White labeling', 'wp-2fa' ),
'default' => false,
'description' => sprintf(
'<p class="description">%1$s <a href="mailto:support@melapress.com">%2$s</a></p>',
\esc_html__( 'Use the settings below to configure the emails which are sent to users as part of the 2FA plugin. If you have any questions send us an email at', 'wp-2fa' ),
\esc_html__( 'support@melapress.com', 'wp-2fa' )
),
'class' => 'WP2FA\Admin\SettingsPages\Settings_Page_White_Label',
'method' => 'render',
'network_action' => 'update_wp2fa_network_options',
),
);
/**
* Filter: `Settings tabs`
*
* Gives an option for third parties to alter the plugin settings page
*
* @param array $settings_tabs – Settings tabs.
*/
return \apply_filters( WP_2FA_PREFIX . 'settings_tabs', $settings_tabs );
}
}
}
includes/classes/Admin/SettingsPages/index.php 0000644 00000000046 15075513060 0015441 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/SettingsPages/class-settings-page-general.php 0000644 00000026225 15075513060 0021631 0 ustar 00 <?php
/**
* Generals settings class.
*
* @package wp2fa
* @subpackage settings-pages
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\SettingsPages;
use WP2FA\Utils\Debugging;
use WP2FA\Utils\Settings_Utils;
use WP2FA\WP2FA;
/*
* General settings tab
*/
if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Settings_Page_General' ) ) {
/**
* Settings_Page_General - Class for handling general settings.
*
* @since 2.0.0
*/
class Settings_Page_General {
/**
* Renders the settings.
*
* @return void
*
* @since 2.0.0
*/
public static function render() {
settings_fields( WP_2FA_SETTINGS_NAME );
self::no_method_exists();
self::disable_brute_force_settings();
self::limit_settings_access();
self::remove_data_upon_uninstall();
submit_button( null, 'primary', WP_2FA_SETTINGS_NAME . '[submit]' );
}
/**
* Validate options before saving.
*
* @param array $input The settings array.
*
* @return array|void
*/
public static function validate_and_sanitize( $input ) {
// Bail if user doesn't have permissions to be here.
if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['action'] ) && ! check_admin_referer( 'wp2fa-step-choose-method' ) ) {
return;
}
Debugging::log( 'The following settings will be processed (General): ' . "\n" . wp_json_encode( $input ) );
$simple_settings_we_can_loop = array(
'enable_destroy_session',
'limit_access',
'brute_force_disable',
'delete_data_upon_uninstall',
'method_invalid_setting',
);
/**
* Gives the ability to change the default general settings.
*
* @param array $general_settings - The array with the default settings.
*
* @since 2.0.0
*/
$simple_settings_we_can_loop = \apply_filters( WP_2FA_PREFIX . 'loop_general_settings', $simple_settings_we_can_loop );
$settings_to_turn_into_bools = array(
'enable_destroy_session',
'limit_access',
'brute_force_disable',
'delete_data_upon_uninstall',
);
foreach ( $simple_settings_we_can_loop as $simple_setting ) {
if ( ! in_array( $simple_setting, $settings_to_turn_into_bools, true ) ) {
// Is item is not one of our possible settings we want to turn into a bool, process.
$output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? trim( (string) sanitize_text_field( $input[ $simple_setting ] ) ) : false;
} else {
// This item is one we treat as a bool, so process correctly.
$output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? true : false;
}
}
if ( isset( $input['2fa_settings_last_updated_by'] ) && ! empty( $input['2fa_settings_last_updated_by'] ) ) {
$policies = WP2FA::get_wp2fa_setting();
if ( false === $policies ) {
$policies = WP2FA::get_default_settings();
}
$policies['2fa_settings_last_updated_by'] = (int) get_current_user_id();
WP2FA::update_plugin_settings( $policies );
}
// Remove duplicates from settings errors. We do this as this sanitization callback is actually fired twice, so we end up with duplicates when saving the settings for the FIRST TIME only. The issue is not present once the settings are in the DB as the sanitization wont fire again. For details on this core issue - https://core.trac.wordpress.org/ticket/21989.
global $wp_settings_errors;
if ( isset( $wp_settings_errors ) ) {
$errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
$wp_settings_errors = $errors; // phpcs:ignore
}
/**
* Filter the values we are about to store in the plugin settings.
*
* @param array $output - The output array with all the data we will store in the settings.
* @param array $input - The input array with all the data we received from the user.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'filter_output_content_general_settings', $output, $input );
// We have overridden any defaults by now so can clear this.
Settings_Utils::delete_option( WP_2FA_PREFIX . 'default_settings_applied' );
Debugging::log( 'The following settings are being saved (General): ' . "\n" . wp_json_encode( $output ) );
return $output;
}
/**
* Updates global settings network options.
*
* @return void
*
* @SuppressWarnings(PHPMD.ExitExpressions)
*/
public static function update_wp2fa_network_options() {
if ( isset( $_POST[ WP_2FA_SETTINGS_NAME ] ) ) {
check_admin_referer( 'wp_2fa_settings-options' );
$options = self::validate_and_sanitize(wp_unslash($_POST[WP_2FA_SETTINGS_NAME])); // phpcs:ignore
$settings_errors = get_settings_errors( WP_2FA_SETTINGS_NAME );
if ( ! empty( $settings_errors ) ) {
// redirect back to our options page.
wp_safe_redirect(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'wp_2fa_network_settings_error' => urlencode_deep( $settings_errors[0]['message'] ),
),
network_admin_url( 'settings.php' )
)
);
exit;
}
WP2FA::update_plugin_settings( $options, false, WP_2FA_SETTINGS_NAME );
// redirect back to our options page.
wp_safe_redirect(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'tab' => 'generic-settings',
'wp_2fa_network_settings_updated' => 'true',
),
network_admin_url( 'admin.php' )
)
);
exit;
}
}
/**
* Limit settings setting.
*
* @return void
*
* @since 2.0.0
*/
private static function remove_data_upon_uninstall() {
?>
<div class="danger-zone-wrapper">
<h3><?php \esc_html_e( 'Do you want to delete the plugin data from the database upon uninstall', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'The plugin saves its settings in the WordPress database. By default the plugin settings are kept in the database so if it is installed again, you do not have to reconfigure the plugin. Enable this setting to delete the plugin settings from the database upon uninstall.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="delete_data"><?php \esc_html_e( 'Delete data', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<input type="checkbox" id="delete_data" name="wp_2fa_settings[delete_data_upon_uninstall]" value="delete_data_upon_uninstall"
<?php checked( 1, WP2FA::get_wp2fa_general_setting( 'delete_data_upon_uninstall' ), true ); ?>
>
<?php \esc_html_e( 'Delete data upon uninstall', 'wp-2fa' ); ?>
</fieldset>
</td>
</tr>
</tbody>
</table>
</div>
<?php
$last_user_to_update_settings = get_current_user_id();
?>
<input type="hidden" id="2fa_main_user" name="wp_2fa_settings[2fa_settings_last_updated_by]" value="<?php echo \esc_attr( $last_user_to_update_settings ); ?>">
<?php
}
/**
* Limit settings setting.
*
* @return void
*
* @since 2.0.0
*/
private static function limit_settings_access() {
?>
<br>
<h3><?php \esc_html_e( 'Limit 2FA settings access', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'Use this setting to hide this plugin configuration area from all other admins.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="limit_access"><?php \esc_html_e( 'Limit access to 2FA settings', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<input type="checkbox" id="limit_access" name="wp_2fa_settings[limit_access]" value="limit_access"
<?php checked( 1, WP2FA::get_wp2fa_general_setting( 'limit_access' ), true ); ?>
>
<?php \esc_html_e( 'Hide settings from other administrators', 'wp-2fa' ); ?>
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
}
/**
* Disable brute force setting.
*
* @return void
*
* @since 2.5.0
*/
private static function disable_brute_force_settings() {
?>
<br>
<h3><?php \esc_html_e( 'Disable 2FA code brute force protection', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'When using email and SMS 2FA, the plugin sends the users a new one-time code whenever they enter the wrong code when logging in. This is a security enhancement, a sort of brute force protection. You can disable this feature from the below setting, however, it is not recommended.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="brute_force_disable"><?php \esc_html_e( 'Disable one-time code brute force protection', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<input type="checkbox" id="brute_force_disable" name="wp_2fa_settings[brute_force_disable]" value="brute_force_disable"
<?php checked( 1, WP2FA::get_wp2fa_general_setting( 'brute_force_disable' ), true ); ?>
>
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
}
/**
* Rendering settings when there are no methods.
*
* @return void
*
* @since 2.2.0
*/
private static function no_method_exists() {
?>
<p class="description">
<?php
printf(
// translators: support email.
\esc_html__( 'Use this setting below to configure the properties of the two-factor authentication on your website and how users use it. If you have any questions send us an email at %1$s.', 'wp-2fa' ),
'<a href="mailto:support@melapress.com">support@melapress.com</a>'
);
?>
</p>
<h3><?php \esc_html_e( 'What should the plugin do if the 2FA method used during a user login is unavailable', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'There may be cases in which the 2FA service is unavailable when a user is trying to log in. For example, the service is unreachable or there are no credits to complete the action. In this case you can configure the plugin to either block the login process, or allow the user to log in without 2FA authentication.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="no-methods"><?php \esc_html_e( 'Select action', 'wp-2fa' ); ?></label></th>
<td>
<fieldset class="contains-hidden-inputs" id="no-methods">
<label for="login_block">
<input type="radio" name="wp_2fa_settings[method_invalid_setting]" id="login_block" value="login_block"
<?php checked( WP2FA::get_wp2fa_general_setting( 'method_invalid_setting' ), 'login_block' ); ?>
>
<span><?php \esc_html_e( 'Block the login.', 'wp-2fa' ); ?></span>
</label>
<br/>
<label for="allow_login_without_method">
<input type="radio" name="wp_2fa_settings[method_invalid_setting]" id="allow_login_without_method" value="allow_login_without_method"
<?php checked( WP2FA::get_wp2fa_general_setting( 'method_invalid_setting' ), 'allow_login_without_method' ); ?>
data-unhide-when-checked=".custom-from-inputs">
<span><?php \esc_html_e( 'Allow the login without 2FA', 'wp-2fa' ); ?></span>
</label>
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
}
}
}
includes/classes/Admin/SettingsPages/class-settings-page-policies.php 0000644 00000106164 15075513060 0022024 0 ustar 00 <?php
/**
* Policy settings class.
*
* @package wp2fa
* @subpackage settings-pages
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\SettingsPages;
use WP2FA\WP2FA;
use WP2FA\Methods\TOTP;
use WP2FA\Methods\Email;
use WP2FA\Utils\Debugging;
use WP2FA\Admin\Settings_Page;
use WP2FA\Utils\Generate_Modal;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Views\First_Time_Wizard_Steps;
/*
* Policies settings tab
*/
if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Settings_Page_Policies' ) ) {
/**
* Settings_Page_Policies - Class for handling settings.
*
* @since 2.0.0
*/
class Settings_Page_Policies {
/**
* Renders the settings.
*
* @return void
*
* @since 2.0.0
*/
public static function render() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$user = wp_get_current_user();
if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
$main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
} else {
$main_user = get_current_user_id();
}
/**
* Used from user settings controller.
*
* @param bool - Default at this point is false - no user settings.
*
* @since 2.4.0
*/
$roles_controller = \apply_filters( WP_2FA_PREFIX . 'roles_controller_exists', false );
if ( $roles_controller ) {
$roles = WP_Helper::get_roles();
foreach ( $roles as $role ) {
self::new_page_created( $role );
}
} else {
self::new_page_created();
}
$enabled_methods = User_Helper::get_enabled_method_for_user( $user );
if ( empty( $enabled_methods ) ) {
$new_page_modal_content = '<h3>' . \esc_html__( 'Exclude yourself?', 'wp-2fa' ) . '</h3>';
$new_page_modal_content .= '</p>' . \esc_html__( 'You are about to enforce 2FA instantly on all users, including yourself, however you have not yet configured your own 2FA method. What would you like to do?', 'wp-2fa' ) . '</p>';
echo Generate_Modal::generate_modal( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'exclude-self-from-instant-2fa',
false,
$new_page_modal_content, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
array(
'<a href="#" class="wp-2fa-button-secondary button-secondary" data-close-2fa-modal>' . __( 'Continue anyway', 'wp-2fa' ) . '</a>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'<a href="#" class="wp-2fa-button-primary button-primary" data-close-2fa-modal data-user-login-name="' . \esc_attr( $user->user_login ) . '">' . __( 'Exclude myself from 2FA policies', 'wp-2fa' ) . '</a>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
),
false,
'560px'
);
}
?>
<div class="wrap wp-2fa-settings-wrapper wp2fa-form-styles">
<h2><?php \esc_html_e( 'WP 2FA Settings', 'wp-2fa' ); ?></h2>
<hr>
<?php if ( ! empty( WP2FA::get_wp2fa_general_setting( 'limit_access' ) ) && $main_user !== $user->ID ) { ?>
<?php
echo \esc_html__( 'These settings have been disabled by your site administrator, please contact them for further assistance.', 'wp-2fa' );
?>
<?php } else { ?>
<?php
/**
* Fires before the plugin settings rendering.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'before_plugin_settings' );
?>
<?php
if ( WP_Helper::is_multisite() ) {
$action = 'edit.php?action=update_wp2fa_network_options';
} else {
$action = 'options.php';
}
if (! isset($_REQUEST['tab']) || isset($_REQUEST['tab']) && '2fa-settings' === $_REQUEST['tab']) { // phpcs:ignore
?>
<br/>
<?php
printf(
'<p class="description">%1$s <a href="mailto:support@melapress.com">%2$s</a></p>',
\esc_html__( 'Use the settings below to configure the properties of the two-factor authentication on your website and how users use it. If you have any questions send us an email at', 'wp-2fa' ),
\esc_html__( 'support@melapress.com', 'wp-2fa' )
);
?>
<br/>
<?php $total_users = count_users(); ?>
<form id="wp-2fa-admin-settings" action='<?php echo \esc_attr( $action ); ?>' method='post' autocomplete="off" data-2fa-total-users="<?php echo \esc_attr( $total_users['total_users'] ); ?>">
<?php
settings_fields( WP_2FA_POLICY_SETTINGS_NAME );
self::select_method_setting();
self::select_enforcement_policy_setting();
self::excluded_roles_or_users_setting();
if ( WP_Helper::is_multisite() ) {
self::excluded_network_sites();
}
/**
* Fires before grace period HTML rendering settings.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'before_grace_period_settings' );
self::grace_period_setting();
self::user_redirect_after_wizard();
/**
* Fires before user profile period HTML rendering settings.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'before_user_profile_settings' );
self::user_profile_settings();
self::disable_2fa_removal_setting();
submit_button();
?>
</form>
<?php } ?>
<?php } ?>
</div>
<?php
}
/**
* Creates new page for settings (FE only).
*
* @param string $role - The name of the role, empty for global.
*
* @return void
*
* @since 2.0.0
*/
private static function new_page_created( $role = '' ) {
$role = ( is_null( $role ) || empty( $role ) || 'global' === $role ) ? '' : $role;
// Check if new user page has been published.
if ( ! empty( get_transient( WP_2FA_PREFIX . 'new_custom_page_created' . $role ) ) ) {
\delete_transient( WP_2FA_PREFIX . 'new_custom_page_created' . $role );
$new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', '', $role );
if ( empty( $new_page_id ) ) {
$new_page_id = Settings::get_custom_settings_page_id( $role );
}
if ( $new_page_id > 0 ) {
$new_page_permalink = get_permalink( $new_page_id );
$new_page_modal_content = '<h3>' . \esc_html__( 'The plugin created the 2FA settings page with the URL:', 'wp-2fa' ) . '</h3>';
$new_page_modal_content .= '<h4><a target="_blank" href="' . \esc_url( $new_page_permalink ) . '">' . \esc_url( $new_page_permalink ) . '</a></h4>';
$new_page_modal_content .= '<p>' . \esc_html__( 'You can edit this page using the page editor, like you do with all other pages.', 'wp-2fa' );
$new_page_modal_content .= '</p>';
$new_page_modal_content .= sprintf(
/* translators: %s: tag name. */
\esc_html__( 'Use the %s html tag in the email templates to include the URL of the 2FA configuration page when notifying the users to configure two-factor authentication.', 'wp-2fa' ),
'<strong>{2fa_settings_page_url}</strong>'
);
$new_page_modal_content .= '</p>';
echo Generate_Modal::generate_modal( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'new-page-created' . $role, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
false,
$new_page_modal_content, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
array(
'<a href="#" class="wp-2fa-button-primary button-primary" data-close-2fa-modal>' . __( 'OK', 'wp-2fa' ) . '</a>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
),
true,
'560px'
);
}
}
}
/**
* Validate options before saving.
*
* @param array $input The settings array.
*
* @return array|void
*
* @since 2.0.0
*/
public static function validate_and_sanitize( $input ) {
Debugging::log( 'The following settings will be processed (Policy): ' . "\n" . wp_json_encode( $input ) );
/*
* Adds the ability to check the referer and act accordingly.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'change_referer' );
// Bail if user doesn't have permissions to be here.
if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['action'] ) && ! check_admin_referer( 'wp2fa-step-choose-method' ) ) {
return;
}
$no_method_enabled = false;
if ( ! isset( $input[ TOTP::POLICY_SETTINGS_NAME ] ) && ! isset( $input[ Email::POLICY_SETTINGS_NAME ] ) && ! isset( $_POST['save_step'] ) ) {
/**
* At this point, none of the default providers is set / activated. This filter allows additional providers to change the behavior. Checking the input array for specific values (methods), and based on that we can raise error that none of the allowed methods has bees selected by the user, or dismiss the error otherwise.
*
* @param bool - Default at this point is true - no method is selected.
* @param array $input - The input array with all the data.
*
* @since 2.0.0
*/
$no_methods_set = \apply_filters( WP_2FA_PREFIX . 'save_additional_enabled_methods', true, $input );
if ( $no_methods_set ) {
add_settings_error(
WP_2FA_POLICY_SETTINGS_NAME,
\esc_attr( 'enable_email_settings_error' ),
\esc_html__( 'No global 2FA methods enabled.', 'wp-2fa' ),
'error'
);
$no_method_enabled = true;
}
}
$simple_settings_we_can_loop = array(
'grace-policy',
'enable_destroy_session',
'2fa_settings_last_updated_by',
'limit_access',
'hide_remove_button',
'redirect-user-custom-page',
'redirect-user-custom-page-global',
'superadmins-role-add',
'superadmins-role-exclude',
'separate-multisite-page-url',
);
/**
* Gives the ability to filter the settings array of the plugin.
*
* @param array $settings - The array with all the default settings.
*
* @since 2.0.0
*/
$simple_settings_we_can_loop = \apply_filters( WP_2FA_PREFIX . 'loop_settings', $simple_settings_we_can_loop );
$settings_to_turn_into_bools = array(
'enable_destroy_session',
'limit_access',
'hide_remove_button',
);
$settings_to_turn_into_array = array(
'enforced_roles',
'enforced_users',
'excluded_users',
'excluded_roles',
'excluded_sites',
);
foreach ( $simple_settings_we_can_loop as $simple_setting ) {
if ( ! in_array( $simple_setting, $settings_to_turn_into_bools, true ) ) {
// Is item is not one of our possible settings we want to turn into a bool, process.
$output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? trim( (string) sanitize_text_field( $input[ $simple_setting ] ) ) : false;
} else {
// This item is one we treat as a bool, so process correctly.
$output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? true : false;
}
}
if ( $no_method_enabled ) {
/**
* No methods are enabled - return the previous selection. Gives the ability for external providers to set the default values.
*
* @param array $output - The output array with all the data we will store in the settings.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'no_method_enabled', $output );
}
$output['included_sites'] = array();
if ( WP_Helper::is_multisite() ) {
if ( isset( $input['included_sites'] ) && is_array( $input['included_sites'] ) && ! empty( $input['included_sites'] ) ) {
foreach ( $input['included_sites'] as &$site ) {
if ( ! filter_var( $site, FILTER_VALIDATE_INT ) ) {
unset( $site );
continue;
}
$output['included_sites'][] = $site;
}
unset( $site );
} elseif ( isset( $input['enforcement-policy'] ) && 'enforce-on-multisite' === $input['enforcement-policy'] && empty( $input['included_sites'] ) ) {
add_settings_error(
WP_2FA_POLICY_SETTINGS_NAME,
\esc_attr( 'included_sites_settings_error' ),
\esc_html__( 'You must specify at least one sub-site', 'wp-2fa' ),
'error'
);
}
}
foreach ( $settings_to_turn_into_array as $setting ) {
if ( isset( $input[ $setting ] ) ) {
$output[ $setting ] = $input[ $setting ];
} else {
$output[ $setting ] = array();
}
}
if ( isset( $input['grace-period'] ) ) {
if ( 0 === (int) $input['grace-period'] ) {
add_settings_error(
WP_2FA_POLICY_SETTINGS_NAME,
\esc_attr( 'grace_settings_error' ),
\esc_html__( 'Grace period must be at least 1 day/hour', 'wp-2fa' ),
'error'
);
$output['grace-period'] = 1;
} else {
$output['grace-period'] = (int) $input['grace-period'];
}
}
if ( isset( $input['grace-period-denominator'] ) && 'days' === $input['grace-period-denominator'] || isset( $input['grace-period-denominator'] ) && 'hours' === $input['grace-period-denominator'] || isset( $input['grace-period-denominator'] ) && 'seconds' === $input['grace-period-denominator'] ) {
$output['grace-period-denominator'] = sanitize_text_field( $input['grace-period-denominator'] );
}
if ( ( isset( $input['create-custom-user-page'] ) && 'yes' === $input['create-custom-user-page'] ) || ( isset( $input['create-custom-user-page'] ) && 'no' === $input['create-custom-user-page'] ) ) {
$output['create-custom-user-page'] = sanitize_text_field( $input['create-custom-user-page'] );
}
if ( ( isset( $input['create-custom-user-page'] ) && 'yes' === $input['create-custom-user-page'] ) && isset( $input['custom-user-page-url'] ) && ! empty( $input['custom-user-page-url'] ) ) {
if ( WP2FA::get_wp2fa_setting( 'custom-user-page-url' ) !== $input['custom-user-page-url'] ) {
if ( 'yes' === $input['create-custom-user-page'] && ! empty( $input['custom-user-page-url'] ) ) {
$output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
if ( WP_Helper::is_multisite() && isset( $input['separate-multisite-page-url'] ) ) {
$sites = WP_Helper::get_multi_sites();
foreach ( $sites as $site ) {
$blog_id = $site->id;
\switch_to_blog( $blog_id );
self::generate_custom_user_profile_page( $output['custom-user-page-url'] );
\restore_current_blog();
}
} else {
self::generate_custom_user_profile_page( $output['custom-user-page-url'] );
}
}
} else {
$output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
$output['custom-user-page-id'] = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
if ( is_null( get_post( $output['custom-user-page-id'] ) ) ) {
$create_page = self::generate_custom_user_profile_page( $output['custom-user-page-url'] );
$output['custom-user-page-id'] = (int) $create_page;
}
}
}
if ( isset( $_REQUEST['page'] ) && 'wp-2fa-setup' !== $_REQUEST['page'] || isset( $_REQUEST[ WP_2FA_POLICY_SETTINGS_NAME ]['create-custom-user-page'] ) ) {
if ( isset( $input['create-custom-user-page'] ) && 'no' === $input['create-custom-user-page'] ) {
$output['custom-user-page-url'] = '';
$output['custom-user-page-id'] = '';
$output['separate-multisite-page-url'] = '';
\wp_delete_post( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ), true );
}
}
if ( isset( $input['create-custom-user-page'] ) && 'yes' === $input['create-custom-user-page'] && empty( $input['custom-user-page-url'] ) ) {
add_settings_error(
WP_2FA_POLICY_SETTINGS_NAME,
\esc_attr( 'no_page_slug_provided' ),
\esc_html__( 'You must provide a new page slug.', 'wp-2fa' ),
'error'
);
}
if ( isset( $input['grace-period'] ) && isset( $input['grace-period-denominator'] ) ) {
// Turn inputs into a useable string.
$create_a_string = $output['grace-period'] . ' ' . $output['grace-period-denominator'];
// Turn that string into a time.
$grace_expiry = strtotime( $create_a_string );
$output['grace-period-expiry-time'] = sanitize_text_field( $grace_expiry );
}
// Process main policy.
if ( isset( $input['enforcement-policy'] ) && in_array( $input['enforcement-policy'], array( 'all-users', 'certain-users-only', 'certain-roles-only', 'do-not-enforce', 'superadmins-only', 'superadmins-siteadmins-only', 'enforce-on-multisite' ), true ) ) {
// Clear enforced roles/users if setting has changed.
if ( 'all-users' === $input['enforcement-policy'] || 'do-not-enforce' === $input['enforcement-policy'] ) {
$input['enforced_users'] = array();
$input['enforced_roles'] = array();
$output['enforced_users'] = array();
$output['enforced_roles'] = array();
$output['superadmins-role-add'] = 'no';
}
$output['enforcement-policy'] = sanitize_text_field( $input['enforcement-policy'] );
if ( 'certain-roles-only' === $input['enforcement-policy'] && empty( $input['enforced_roles'] ) && empty( $input['enforced_users'] ) ) {
add_settings_error(
WP_2FA_POLICY_SETTINGS_NAME,
\esc_attr( 'enforced_roles_settings_error' ),
\esc_html__( 'You must specify at least one role or user', 'wp-2fa' ),
'error'
);
$output['enforcement-policy'] = 'do-not-enforce';
}
// If any users are being excluded, delete any wp 2fa data.
if ( isset( $output['excluded_users'] ) &&
! empty( array_diff( (array) WP2FA::get_wp2fa_setting( 'excluded_users' ), (array) $output['excluded_users'] ) ) ) {
// Wipe user 2fa data.
$user_array = $output['excluded_users'];
foreach ( $user_array as $user ) {
if ( ! empty( $user ) ) {
$user_to_wipe = get_user_by( 'login', $user );
global $wpdb;
// @codingStandardsIgnoreStart
$wpdb->query(
$wpdb->prepare(
"
DELETE FROM $wpdb->usermeta
WHERE user_id = %d
AND meta_key LIKE %s
",
array(
$user_to_wipe->ID,
'wp_2fa_%',
)
)
);
// @codingStandardsIgnoreEnd
}
}
}
}
/**
* Allow extensions and 3rd party developers to run extra validation of the output array.
*
* @param array - Array with all the collected data.
*/
do_action( WP_2FA_PREFIX . 'run_extra_settings_validation', $output );
/**
* Filter the values we are about to store in the plugin settings.
*
* @param array $output - The output array with all the data we will store in the settings.
* @param array $input - The input array with all the data we received from the user.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'filter_output_content', $output, $input );
// Remove duplicates from settings errors. We do this as this sanitization callback is actually fired twice, so we end up with duplicates when saving the settings for the FIRST TIME only. The issue is not present once the settings are in the DB as the sanitization wont fire again. For details on this core issue - https://core.trac.wordpress.org/ticket/21989.
global $wp_settings_errors;
if ( isset( $wp_settings_errors ) ) {
$errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
$wp_settings_errors = $errors; // phpcs:ignore
}
/**
* Allow extensions and 3rd party developers to change or check the settings array.
*
* @param array - Array with all the collected and validated data.
*
* @since 2.6.0
*/
do_action( WP_2FA_PREFIX . 'before_settings_save', $output );
// WordPress saves the option to the database, but we still need to do some work when the settings are saved.
WP2FA::update_plugin_settings( $output, true );
Debugging::log( 'The following settings are being saved (Policy): ' . "\n" . wp_json_encode( $output ) );
// We have overridden any defaults by now so can clear this.
Settings_Utils::delete_option( WP_2FA_PREFIX . 'default_settings_applied' );
Settings_Utils::delete_option( 'wizard_not_finished' );
/**
* Notify the extensions and 3rd party developers that the settings array is saved.
*
* @param array - Array with all the stored settings.
*
* @since 2.6.0
*/
do_action( WP_2FA_PREFIX . 'after_settings_save', Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() ) );
return $output;
}
/**
* Updates global policy network options.
*
* @return void
*
* @since 2.0.0
*
* @SuppressWarnings(PHPMD.ExitExpressions)
*/
public static function update_wp2fa_network_options() {
if ( isset( $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) {
check_admin_referer( 'wp_2fa_policy-options' );
$options = self::validate_and_sanitize(wp_unslash($_POST[WP_2FA_POLICY_SETTINGS_NAME])); // phpcs:ignore
$settings_errors = get_settings_errors( WP_2FA_POLICY_SETTINGS_NAME );
if ( ! empty( $settings_errors ) ) {
// redirect back to our options page.
wp_safe_redirect(
add_query_arg(
array(
'page' => Settings_Page::TOP_MENU_SLUG,
'wp_2fa_network_settings_error' => urlencode_deep( $settings_errors[0]['message'] ),
),
network_admin_url( 'admin.php' )
)
);
exit;
}
WP2FA::update_plugin_settings( $options );
// redirect back to our options page.
wp_safe_redirect(
add_query_arg(
array(
'page' => Settings_Page::TOP_MENU_SLUG,
'wp_2fa_network_settings_updated' => 'true',
),
network_admin_url( 'admin.php' )
)
);
exit;
}
}
/**
* Creates a new page with our shortcode present.
*
* @param string $page_slug - The page slug.
* @param string $role - The name of the role for which the page has been created.
*
* @return mixed
*
* @since 2.0.0
*/
public static function generate_custom_user_profile_page( $page_slug, string $role = '' ) {
// Check if a page with slug exists.
$page_exists = self::get_post_by_post_name( $page_slug, 'page' );
if ( $page_exists ) {
// Seeing as the page exists, return its ID.
return $page_exists->ID;
}
$generated_by_message = '<p>' . \esc_html__( 'Page generated by', 'wp-2fa' );
$generated_by_message .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'WP 2FA Plugin', 'wp-2fa' ) . '</a>';
$generated_by_message .= '</p>';
$user = wp_get_current_user();
$post_data = array(
'post_title' => 'WP 2FA User Profile',
'post_name' => $page_slug,
'post_content' => '[wp-2fa-setup-form] ' . $generated_by_message,
'post_status' => 'publish',
'post_author' => $user->ID,
'post_type' => 'page',
);
// Lets insert the post now.
$result = wp_insert_post( $post_data );
if ( $result && ! is_wp_error( $result ) ) {
$post_id = $result;
set_transient( WP_2FA_PREFIX . 'new_custom_page_created' . $role, true, 60 );
set_site_transient( WP_2FA_PREFIX . 'new_custom_page_created' . $role, true, 60 );
return $post_id;
}
return $result;
}
/**
* Check if page with slug exists.
*
* @param string $slug - The post slug.
* @param string $post_type - Post type.
*
* @return \WP_Post|bool
*
* @since 2.0.0
*/
public static function get_post_by_post_name( $slug = '', $post_type = '' ) {
if ( ! $slug || ! $post_type ) {
return false;
}
$post_object = get_page_by_path( $slug, OBJECT, $post_type );
if ( ! $post_object ) {
return false;
}
return $post_object;
}
/**
* General settings.
*
* @return void
*
* @since 2.0.0
*/
private static function select_method_setting() {
First_Time_Wizard_Steps::select_method( false );
}
/**
* Policy settings.
*
* @return void
*
* @since 2.0.0
*/
private static function select_enforcement_policy_setting() {
First_Time_Wizard_Steps::enforcement_policy( false );
}
/**
* User profile settings.
*
* @return void
*
* @since 2.0.0
*/
private static function user_profile_settings() {
ob_start();
$create_page = WP2FA::get_wp2fa_setting( 'create-custom-user-page' );
?>
<h3><?php \esc_html_e( 'Can users access the WordPress dashboard or you have custom profile pages? ', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'If your users do not have access to the WordPress dashboard (because you use custom user profile pages) enable this option. Once enabled, the plugin creates a page which ONLY authenticated users can access to configure their user 2FA settings. A link to this page is sent in the 2FA welcome email.', 'wp-2fa' ); ?></a>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="use_custom_page"><?php \esc_html_e( 'Frontend 2FA settings page', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<label class="radio-inline">
<input id="use_custom_page" type="radio" name="wp_2fa_policy[create-custom-user-page]" value="yes"
<?php checked( $create_page, 'yes' ); ?>
>
<?php \esc_html_e( 'Yes', 'wp-2fa' ); ?>
</label>
<label class="radio-inline">
<input id="dont_use_custom_page" type="radio" name="wp_2fa_policy[create-custom-user-page]" value="no"
<?php checked( $create_page, 'no' ); ?>
<?php checked( $create_page, '' ); ?>
>
<?php \esc_html_e( 'No', 'wp-2fa' ); ?>
</label>
</fieldset>
</td>
</tr>
<tr class="custom-user-page-setting<?php echo ( 'yes' !== $create_page ) ? ' disabled' : ''; ?>">
<th><label for="custom-user-page-url"><?php \esc_html_e( 'Frontend 2FA settings page URL', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<?php
if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
$custom_slug = \get_post_field( 'post_name', \get_post( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) );
} else {
$custom_slug = WP2FA::get_wp2fa_setting( 'custom-user-page-url' );
}
$has_error = false;
$settings_errors = \get_settings_errors( WP_2FA_SETTINGS_NAME );
if ( ! empty( $settings_errors ) ) {
foreach ( $settings_errors as $error ) {
if ( 'no_page_slug_provided' === $error['code'] ) {
$has_error = true;
break;
}
}
}
?>
<?php echo \esc_html( trailingslashit( get_site_url() ) ); ?>
<input type="text" id="custom-user-page-url" name="wp_2fa_policy[custom-user-page-url]" value="<?php echo \esc_attr( sanitize_text_field( $custom_slug ) ); ?>"
<?php echo ( $has_error ) ? ' class="error"' : ''; ?>>
</fieldset>
<?php
if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) && ! WP_Helper::is_multisite() ) ) {
$edit_post_link = \get_edit_post_link( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) );
$view_post_link = \get_permalink( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) );
?>
<br>
<a href="<?php echo \esc_url( $edit_post_link ); ?>" target="_blank" class="button button-secondary" style="margin-right: 5px;"><?php \esc_html_e( 'Edit Page', 'wp-2fa' ); ?></a> <a href="<?php echo \esc_url( $view_post_link ); ?>" target="_blank" class="button button-primary"><?php \esc_html_e( 'View Page', 'wp-2fa' ); ?></a>
<?php
}
?>
</td>
</tr>
<?php if ( WP_Helper::is_multisite() ) { ?>
<tr class="custom-user-page-setting<?php echo ( 'yes' !== $create_page ) ? ' disabled' : ''; ?>">
<th><label for="separate-multisite-page-url"><?php \esc_html_e( 'Create separate pages on multisite network', 'wp-2fa' ); ?></label></th>
<td>
<?php
$separate_multisite_page = WP2FA::get_wp2fa_setting( 'separate-multisite-page-url' );
?>
<fieldset>
<input type="checkbox" name="wp_2fa_policy[<?php echo \esc_attr( 'separate-multisite-page-url' ); ?>]"
id="separate-multisite-page-url"
value="separate-multisite-page-url" <?php checked( $separate_multisite_page, 'separate-multisite-page-url' ); ?> class="js-nested">
<label for="separate-multisite-page-url"><?php echo \esc_html__( 'Create User settings page separately for every site', 'wp-2fa' ); ?></label>
<p class="description"><?php echo \esc_html__( 'When you enable this setting a page with the same slug is created on each site on the network, so the users of each sub site can use this page on their website to configure 2FA.', 'wp-2fa' ); ?></p>
</fieldset>
</td>
</tr>
<?php } ?>
<tr class="custom-user-page-setting<?php echo ( 'yes' !== $create_page ) ? ' disabled' : ''; ?>">
<th colspan="2"><p class="description"><?php \esc_html_e( 'Specify the page where you want to redirect your users to after they complete the 2FA setup. This will override the global redirect setting.', 'wp-2fa' ); ?></p></th>
</tr>
<tr class="custom-user-page-setting<?php echo ( 'yes' !== $create_page ) ? ' disabled' : ''; ?>">
<th><label for="redirect-user-custom-page"><?php \esc_html_e( 'Redirect users after 2FA setup', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<?php
$custom_slug = WP2FA::get_wp2fa_setting( 'redirect-user-custom-page' );
?>
<?php echo \esc_html( trailingslashit( get_site_url() ) ); ?>
<input type="text" id="redirect-user-custom-page" name="wp_2fa_policy[redirect-user-custom-page]" value="<?php echo \esc_attr( sanitize_text_field( $custom_slug ) ); ?>">
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
$output = ob_get_clean();
/**
* Gives the ability to manipulate the output.
*
* @param string $output - Parsed HTML with the methods.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'user_profile_settings', $output );
echo $output; // phpcs:ignore
}
/**
* User profile settings.
*
* @return void
*
* @since 2.0.0
*/
private static function user_redirect_after_wizard() {
ob_start();
?>
<h3><?php \esc_html_e( 'Do you want to redirect the user to a specific page after completing the 2FA setup wizard', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'Specify a URL of a page where you want to redirect the users once they complete the 2FA setup wizard. Leave empty for default behaviour, in which users are redirected back to the page from where they launched the wizard.', 'wp-2fa' ); ?></a>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="redirect-user-custom-page-global"><?php \esc_html_e( 'Redirect users after 2FA setup to', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<?php echo \esc_html( trailingslashit( get_site_url() ) ); ?>
<input type="text" id="redirect-user-custom-page-global" name="wp_2fa_policy[redirect-user-custom-page-global]" value="<?php echo \esc_attr( sanitize_text_field( WP2FA::get_wp2fa_setting( 'redirect-user-custom-page-global' ) ) ); ?>">
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
$output = ob_get_clean();
/**
* Gives the ability to manipulate the output.
*
* @param string $output - Parsed HTML with the methods.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'redirect_after', $output );
echo $output; // phpcs:ignore
}
/**
* Role and users exclusion settings.
*
* @return void
*
* @since 2.0.0
*/
private static function excluded_roles_or_users_setting() {
?>
<div id="exclusion_settings_wrapper">
<?php First_Time_Wizard_Steps::exclude_users(); ?>
</div>
<?php
}
/**
* Role and users exclusion settings.
*
* @return void
*
* @since 2.0.0
*/
private static function excluded_network_sites() {
First_Time_Wizard_Steps::excluded_network_sites();
}
/**
* Grace period settings.
*
* @return void
*
* @since 2.0.0
*/
private static function grace_period_setting() {
ob_start();
/**
* Fires after the grace period. Gives the ability to change the parsed code.
*
* @param string $content - HTML content.
* @param string $role - The name of the role.
* @param string $name_prefix - Name prefix for the input name, includes the role name if provided.
* @param string $data_role - Data attribute - used by the JS.
* @param string $role_id - The role name, used to identify the inputs.
*
* @since 2.0.0
*/
echo \apply_filters( WP_2FA_PREFIX . 'before_grace_period_settings', '', '', 'wp_2fa_policy' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<br>
<h3><?php \esc_html_e( 'Should users be asked to setup 2FA instantly or should they have a grace period?', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'When you enforce 2FA on users they have a grace period to configure 2FA. If they fail to configure it within the configured stipulated time, their account will be locked and have to be unlocked manually. Note that user accounts cannot be unlocked automatically, even if you change the settings. As a security precaution they always have to be unlocked them manually. Maximum grace period is 10 days.', 'wp-2fa' ); ?> <a href="https://melapress.com/support/kb/configure-grace-period-2fa/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank"><?php \esc_html_e( 'Learn more.', 'wp-2fa' ); ?></a>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="grace-policy"><?php \esc_html_e( 'Grace period', 'wp-2fa' ); ?></label></th>
<td>
<?php First_Time_Wizard_Steps::grace_period( true ); ?>
</td>
</tr>
</tbody>
</table>
<?php
$output = ob_get_clean();
/**
* Gives the ability to manipulate the output.
*
* @param string $output - Parsed HTML with the methods.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'grace_period', $output );
echo $output; // phpcs:ignore
}
/**
* Disable removal of 2FA settings.
*
* @return void
*
* @since 2.0.0
*/
private static function disable_2fa_removal_setting() {
ob_start();
?>
<br>
<h3><?php \esc_html_e( 'Should users be allowed to disable 2FA from their user profile?', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'Users can configure and also disable 2FA on their profile by clicking the "Remove 2FA" button. Enable this setting to disable the Remove 2FA button so users cannot disable 2FA from their user profile.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="hide-remove-2fa"><?php \esc_html_e( 'Hide the Remove 2FA button', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<input type="checkbox" id="hide-remove-2fa" name="wp_2fa_policy[hide_remove_button]" value="hide_remove_button"
<?php checked( 1, WP2FA::get_wp2fa_setting( 'hide_remove_button' ), true ); ?>
>
<?php \esc_html_e( 'Hide the Remove 2FA button on user profile pages', 'wp-2fa' ); ?>
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
$output = ob_get_clean();
/**
* Gives the ability to manipulate the output.
*
* @param string $output - Parsed HTML with the methods.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'disable_2fa', $output );
echo $output; // phpcs:ignore
}
}
}
includes/classes/Admin/SettingsPages/class-settings-page-white-label.php 0000644 00000041233 15075513060 0022405 0 ustar 00 <?php
/**
* White label settings class.
*
* @package wp2fa
* @subpackage settings-pages
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\SettingsPages;
use WP2FA\WP2FA;
use WP2FA\Utils\Debugging;
use WP2FA\Extensions\WhiteLabeling\White_Labeling_Render;
/**
* White labeling settings tab
*/
if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Settings_Page_White_Label' ) ) {
/**
* Settings_Page_White_Label - Class for handling settings
*
* @since 2.0.0
*/
class Settings_Page_White_Label {
/**
* Render the settings
*
* @return void
*
* @since 2.0.0
*/
public static function render() {
settings_fields( WP_2FA_WHITE_LABEL_SETTINGS_NAME );
self::white_labelling_tabs_wrapper();
submit_button();
}
/**
* Validate options before saving
*
* @param array $input The settings array.
*
* @return array|void
*
* @since 2.0.0
*/
public static function validate_and_sanitize( $input ) {
// Bail if user doesn't have permissions to be here.
if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['action'] ) && ! check_admin_referer( 'wp2fa-step-choose-method' ) ) {
return;
}
Debugging::log( 'The following settings will be processed (White Label): ' . "\n" . wp_json_encode( $input ) );
$output['default-text-code-page'] = WP2FA::get_wp2fa_white_label_setting( 'default-text-code-page', false, false );
if ( isset( $input['default-text-code-page'] ) && '' !== trim( (string) $input['default-text-code-page'] ) ) {
$output['default-text-code-page'] = \wp_kses_post( $input['default-text-code-page'] );
}
$output['default-backup-code-page'] = WP2FA::get_wp2fa_white_label_setting( 'default-backup-code-page', false, false );
if ( isset( $input['default-backup-code-page'] ) && '' !== trim( (string) $input['default-backup-code-page'] ) ) {
$output['default-backup-code-page'] = \wp_strip_all_tags( $input['default-backup-code-page'] );
}
$output['login-to-view-area'] = WP2FA::get_wp2fa_white_label_setting( 'login-to-view-area', false, false );
if ( isset( $input['login-to-view-area'] ) && '' !== trim( (string) $input['login-to-view-area'] ) ) {
$output['login-to-view-area'] = \wp_strip_all_tags( $input['login-to-view-area'] );
}
$output['use_custom_2fa_message'] = WP2FA::get_wp2fa_white_label_setting( 'use_custom_2fa_message', false, false );
if ( isset( $input['use_custom_2fa_message'] ) && '' !== trim( (string) $input['use_custom_2fa_message'] ) ) {
$output['use_custom_2fa_message'] = \wp_strip_all_tags( $input['use_custom_2fa_message'] );
}
$output['custom-text-app-code-page'] = WP2FA::get_wp2fa_white_label_setting( 'custom-text-app-code-page', false, false );
$output['custom-text-email-code-page'] = WP2FA::get_wp2fa_white_label_setting( 'custom-text-email-code-page', false, false );
$output['custom-text-authy-code-page-intro'] = WP2FA::get_wp2fa_white_label_setting( 'custom-text-authy-code-page-intro', false, false );
$output['custom-text-authy-code-page-awaiting'] = WP2FA::get_wp2fa_white_label_setting( 'custom-text-authy-code-page-awaiting', false, false );
$output['custom-text-authy-code-page'] = WP2FA::get_wp2fa_white_label_setting( 'custom-text-authy-code-page', false, false );
$output['custom-text-twilio-code-page'] = WP2FA::get_wp2fa_white_label_setting( 'custom-text-twilio-code-page', false, false );
if ( isset( $input['custom-text-app-code-page'] ) && '' !== trim( (string) $input['custom-text-app-code-page'] ) ) {
$output['custom-text-app-code-page'] = \wp_strip_all_tags( $input['custom-text-app-code-page'] );
}
if ( isset( $input['custom-text-email-code-page'] ) && '' !== trim( (string) $input['custom-text-email-code-page'] ) ) {
$output['custom-text-email-code-page'] = \wp_strip_all_tags( $input['custom-text-email-code-page'] );
}
if ( isset( $input['custom-text-authy-code-page'] ) && '' !== trim( (string) $input['custom-text-authy-code-page'] ) ) {
$output['custom-text-authy-code-page'] = \wp_strip_all_tags( $input['custom-text-authy-code-page'] );
}
if ( isset( $input['custom-text-authy-code-page-intro'] ) && '' !== trim( (string) $input['custom-text-authy-code-page-intro'] ) ) {
$output['custom-text-authy-code-page-intro'] = \wp_strip_all_tags( $input['custom-text-authy-code-page-intro'] );
}
if ( isset( $input['custom-text-authy-code-page-awaiting'] ) && '' !== trim( (string) $input['custom-text-authy-code-page-awaiting'] ) ) {
$output['custom-text-authy-code-page-awaiting'] = \wp_strip_all_tags( $input['custom-text-authy-code-page-awaiting'] );
}
if ( isset( $input['custom-text-twilio-code-page'] ) && '' !== trim( (string) $input['custom-text-twilio-code-page'] ) ) {
$output['custom-text-twilio-code-page'] = \wp_strip_all_tags( $input['custom-text-twilio-code-page'] );
}
if ( isset( $_REQUEST['_wp_http_referer'] ) ) {
$request_area = wp_parse_url( \wp_unslash( $_REQUEST['_wp_http_referer'] ) ); // phpcs:ignore
$request_area_path = strpos( $request_area['query'], 'white-label-section' );
// If we have the input POSTed, we are on the right page so grab it.
if ( isset( $input['enable_wizard_styling'] ) && '' !== trim( (string) $input['enable_wizard_styling'] ) ) {
$output['enable_wizard_styling'] = \wp_strip_all_tags( $input['enable_wizard_styling'] );
} else {
// Are we on either the white labelling page (free and premium) or the custom CSS area (premium only)?
if ( ! $request_area_path || $request_area_path && strpos( $request_area['query'], 'custom-css' ) ) {
$output['enable_wizard_styling'] = '';
$input['enable_wizard_styling'] = '';
} else {
$input['enable_wizard_styling'] = WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling', false );
$output['enable_wizard_styling'] = WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling', false );
}
}
if ( isset( $input['show_help_text'] ) && '' !== trim( (string) $input['show_help_text'] ) ) {
$output['show_help_text'] = \wp_strip_all_tags( $input['show_help_text'] );
} else {
// Nothing was POSTed, check where we are in case that means we simple an empty/disabled checkbox.
if ( $request_area_path && ! strpos( $request_area['query'], 'method_selection' ) ) {
$input['show_help_text'] = WP2FA::get_wp2fa_white_label_setting( 'show_help_text', false );
$output['show_help_text'] = WP2FA::get_wp2fa_white_label_setting( 'show_help_text', false );
} else {
$output['show_help_text'] = '';
$input['show_help_text'] = '';
}
}
// Same as above, but for the optional welcome.
if ( isset( $input['enable_welcome'] ) && '' !== trim( (string) $input['enable_welcome'] ) ) {
$output['enable_welcome'] = \wp_strip_all_tags( $input['enable_welcome'] );
} elseif ( strpos( $request_area['query'], 'white-label-sub-section' ) && strpos( $request_area['query'], 'welcome' ) ) {
$input['enable_welcome'] = '';
$output['enable_welcome'] = '';
} else {
$input['enable_welcome'] = WP2FA::get_wp2fa_white_label_setting( 'enable_welcome', false );
$output['enable_welcome'] = WP2FA::get_wp2fa_white_label_setting( 'enable_welcome', false );
}
if ( isset( $input['enable_wizard_logo'] ) && '' !== trim( (string) $input['enable_wizard_logo'] ) ) {
$output['enable_wizard_logo'] = \wp_strip_all_tags( $input['enable_wizard_logo'] );
} elseif ( strpos( $request_area['query'], 'white-label-sub-section' ) && strpos( $request_area['query'], 'welcome' ) ) {
$input['enable_wizard_logo'] = '';
$output['enable_wizard_logo'] = '';
} else {
$input['enable_wizard_logo'] = WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_logo', false );
$output['enable_wizard_logo'] = WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_logo', false );
}
}
if ( isset( $input['login_custom_css'] ) && ! empty( $input['login_custom_css'] ) ) {
if ( preg_match( '#</?\w+#', $input['login_custom_css'] ) ) {
add_settings_error(
WP_2FA_SETTINGS_NAME,
\esc_attr( 'markup_invalid_settings_error' ),
\esc_html__( 'Markup is not allowed in Login area CSS.', 'wp-2fa' ),
'error'
);
$output['login_custom_css'] = WP2FA::get_wp2fa_white_label_setting( 'login_custom_css', false );
$input['login_custom_css'] = WP2FA::get_wp2fa_white_label_setting( 'login_custom_css', false );
} else {
$output['login_custom_css'] = \wp_strip_all_tags( $input['login_custom_css'] );
$input['login_custom_css'] = \wp_strip_all_tags( $input['login_custom_css'] );
}
}
if ( isset( $input['disable_login_css'] ) && '' !== trim( (string) $input['disable_login_css'] ) ) {
$output['disable_login_css'] = \wp_strip_all_tags( $input['disable_login_css'] );
} else {
// Nothing was POSTed, check where we are in case that means we simple an empty/disabled checkbox.
if ( $request_area_path && ! strpos( $request_area['query'], 'method_selection' ) ) {
$input['disable_login_css'] = WP2FA::get_wp2fa_white_label_setting( 'disable_login_css', false );
$output['disable_login_css'] = WP2FA::get_wp2fa_white_label_setting( 'disable_login_css', false );
} else {
$output['disable_login_css'] = '';
$input['disable_login_css'] = '';
}
}
// Remove duplicates from settings errors. We do this as this sanitization callback is actually fired twice, so we end up with duplicates when saving the settings for the FIRST TIME only. The issue is not present once the settings are in the DB as the sanitization wont fire again. For details on this core issue - https://core.trac.wordpress.org/ticket/21989.
global $wp_settings_errors;
if ( isset( $wp_settings_errors ) ) {
$errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
$wp_settings_errors = $errors; // phpcs:ignore
}
/**
* Filter the values we are about to store in the plugin settings.
*
* @param array $output - The output array with all the data we will store in the settings.
* @param array $input - The input array with all the data we received from the user.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'filter_output_content', $output, $input );
Debugging::log( 'The following settings are being saved (White Label): ' . "\n" . wp_json_encode( $output ) );
return $output;
}
/**
* Updates global white label network options
*
* @return void
*
* @since 2.0.0
*
* @SuppressWarnings(PHPMD.ExitExpressions)
*/
public static function update_wp2fa_network_options() {
if ( isset( $_POST[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] ) ) {
check_admin_referer( 'wp_2fa_white_label-options' );
$options = self::validate_and_sanitize( wp_unslash( $_POST[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] ) ); // phpcs:ignore
$settings_errors = get_settings_errors( WP_2FA_WHITE_LABEL_SETTINGS_NAME );
if ( ! empty( $settings_errors ) ) {
// redirect back to our options page.
wp_safe_redirect(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'wp_2fa_network_settings_error' => urlencode_deep( $settings_errors[0]['message'] ),
),
network_admin_url( 'settings.php' )
)
);
exit;
}
WP2FA::update_plugin_settings( $options, false, WP_2FA_WHITE_LABEL_SETTINGS_NAME );
// redirect back to our options page.
wp_safe_redirect(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'tab' => 'white-label-settings',
'wp_2fa_network_settings_updated' => 'true',
),
network_admin_url( 'admin.php' )
)
);
exit;
}
}
/**
* Wrapper which adds special tabbed navigation and content
*
* @return void
*
* @since 2.3.0
*/
private static function white_labelling_tabs_wrapper() {
/**
* Fires right before the white label settings tab HTML, handles tabbed nav.
*
* @since 2.3.0
*/
do_action( WP_2FA_PREFIX . 'white_labeling_tabbed_navigation' );
self::change_default_text_area();
}
/**
* Shows default settings input to the user
*
* @return void
*
* @since 2.0.0
*/
private static function change_default_text_area() {
/**
* Fires right before the white label settings tab HTML rendering.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'white_labeling_settings_page_before_default_text' );
?>
<h3><?php \esc_html_e( 'Change the default text used in the 2FA code page', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'This is the text shown to the users on the page when they are asked to enter the 2FA code. To change the default text, simply type it in the below placeholder.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="2fa-method"><?php \esc_html_e( '2FA code page text', 'wp-2fa' ); ?></label></th>
<td>
<?php
if ( class_exists( 'WP2FA\Extensions\WhiteLabeling\White_Labeling_Render' ) ) {
echo White_Labeling_Render::get_method_text_editor( 'default-text-code-page' ); // phpcs:ignore
} else {
echo self::create_standard_editor( WP2FA::get_wp2fa_white_label_setting( 'default-text-code-page', true ), 'default-text-code-page' );
}
?>
<div style="margin-top: 5px;"><span><strong><i><?php \esc_html_e( 'Note:', 'wp-2fa' ); ?></i></strong> <?php \esc_html_e( 'Only plain text is allowed.', 'wp-2fa' ); ?></span></div>
</td>
</tr>
<tr>
<th><label for="backup-method"><?php \esc_html_e( 'Backup code page text', 'wp-2fa' ); ?></label></th>
<td>
<?php
if ( class_exists( 'WP2FA\Extensions\WhiteLabeling\White_Labeling_Render' ) ) {
echo White_Labeling_Render::get_method_text_editor( 'default-backup-code-page' ); // phpcs:ignore
} else {
echo self::create_standard_editor( WP2FA::get_wp2fa_white_label_setting( 'default-backup-code-page', true ), 'default-backup-code-page' );
}
?>
<div style="margin-top: 5px;"><span><strong><i><?php \esc_html_e( 'Note:', 'wp-2fa' ); ?></i></strong> <?php \esc_html_e( 'Only plain text is allowed.', 'wp-2fa' ); ?></span></div>
</td>
</tr>
<tr>
<th><label for="backup-method"><?php \esc_html_e( 'Text for logged out users trying to access the 2FA configuration page', 'wp-2fa' ); ?></label></th>
<td>
<?php
if ( class_exists( 'WP2FA\Extensions\WhiteLabeling\White_Labeling_Render' ) ) {
echo White_Labeling_Render::get_method_text_editor( 'login-to-view-area' ); // phpcs:ignore
} else {
echo self::create_standard_editor( WP2FA::get_wp2fa_white_label_setting( 'login-to-view-area', true ), 'login-to-view-area' );
}
?>
</td>
</tr>
<?php
/**
* Gives the ability for the 3rd party extensions to add additional white label settings
*/
do_action( WP_2FA_PREFIX . 'white_labeling_settings_page_after_code_page' );
?>
</tbody>
</table>
<h3><?php \esc_html_e( 'Change the styling of the user 2FA wizards', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'By default, the user 2FA wizards which the users see and use to set up 2FA have our own styling. Disable the below setting so the wizards use the styling of your website\'s theme.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="enable_wizard_styling"><?php \esc_html_e( 'Enable styling', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<input type="checkbox" id="enable_wizard_styling" name="wp_2fa_white_label[enable_wizard_styling]" value="enable_wizard_styling"
<?php \checked( 'enable_wizard_styling', WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ), true ); ?>
>
<?php \esc_html_e( 'Enable our CSS within user wizards', 'wp-2fa' ); ?>
</fieldset>
</td>
</tr>
</tbody>
</table>
<?php
/**
* Fires after the white label settings tab is rendered.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'white_labeling_settings_page_after_default_text' );
}
/**
* Simple function to create a neat text editor in free.
*
* @param string $content
* @param string $requested_slide
* @return void
*/
private static function create_standard_editor( $content, $requested_slide ) {
$settings = array(
'media_buttons' => false,
'editor_height' => 200,
'textarea_name' => 'wp_2fa_white_label[' . $requested_slide . ']',
);
if ( isset( $content ) ) {
wp_editor( $content, $requested_slide, $settings );
}
}
}
}
includes/classes/Admin/SettingsPages/class-settings-page-email.php 0000644 00000051174 15075513060 0021304 0 ustar 00 <?php
/**
* Email settings class.
*
* @package wp2fa
* @subpackage settings-pages
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\SettingsPages;
use WP2FA\Email_Template;
use WP2FA\WP2FA;
use WP2FA\Utils\Debugging;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Admin\Settings_Page;
use WP2FA\Extensions\WhiteLabeling\White_Labeling_Render;
/**
* Email settings tab
*/
if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Settings_Page_Email' ) ) {
/**
* Settings_Page_Email - Class for handling email settings
*
* @since 2.0.0
*/
class Settings_Page_Email {
/**
* Render the settings
*
* @return void
*
* @since 2.0.0
*/
public static function render() {
\settings_fields( WP_2FA_EMAIL_SETTINGS_NAME );
self::email_from_settings();
self::email_settings();
\submit_button( \esc_html__( 'Save email settings and templates', 'wp-2fa' ) );
}
/**
* Handle saving email options to the network main site options.
*
* @return void
*
* @since 2.0.0
*
* @SuppressWarnings(PHPMD.ExitExpressions)
*/
public static function update_wp2fa_network_options() {
if ( isset( $_POST['email_from_setting'] ) ) { // phpcs:ignore
$options = self::validate_and_sanitize( wp_unslash( $_POST ) ); // phpcs:ignore
if ( isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_display_name'] ) && empty( $_POST['custom_from_display_name'] ) || isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_email_address'] ) && empty( $_POST['custom_from_email_address'] ) ) { // phpcs:ignore
// redirect back to our options page.
\wp_safe_redirect(
\add_query_arg(
array(
'page' => 'wp-2fa-settings',
'wp_2fa_network_settings_updated' => 'false',
'tab' => 'email-settings',
),
\network_admin_url( 'admin.php' )
)
);
exit;
}
Settings_Utils::update_option( WP_2FA_EMAIL_SETTINGS_NAME, $options );
}
// redirect back to our options page.
\wp_safe_redirect(
\add_query_arg(
array(
'page' => 'wp-2fa-settings',
'wp_2fa_network_settings_updated' => 'true',
'tab' => 'email-settings',
),
\network_admin_url( 'admin.php' )
)
);
exit;
}
/**
* Email settings
*
* @return void
*
* @since 2.0.0
*/
private static function email_from_settings() {
?>
<h3><?php \esc_html_e( 'Which email address should the plugin use as a from address?', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'Use these settings to customize the "from" name and email address for all correspondence sent from our plugin.', 'wp-2fa' ); ?>
</p>
<table class="form-table">
<tbody>
<tr>
<th><label for="2fa-method"><?php \esc_html_e( 'From email & name', 'wp-2fa' ); ?></label>
</th>
<td>
<fieldset class="contains-hidden-inputs">
<label for="use-defaults">
<input type="radio" name="email_from_setting" id="use-defaults" value="use-defaults"
<?php \checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-defaults' ); ?>
>
<span><?php \esc_html_e( 'Use the email address ', 'wp-2fa' ); ?> <?php echo Settings_Page::get_default_email_address(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
</label>
<br/>
<label for="use-custom-email">
<input type="radio" name="email_from_setting" id="use-custom-email" value="use-custom-email"
<?php \checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-custom-email' ); ?>
data-unhide-when-checked=".custom-from-inputs">
<span><?php \esc_html_e( 'Use another email address', 'wp-2fa' ); ?></span>
</label>
<fieldset class="hidden custom-from-inputs">
<p class="description">
<?php \esc_html_e( 'A \'From email\' address with a domain different than that of your website domain name, or with a domain that the hosting does not relay might cause the notification emails to be blocked, marked as spam, or not delivered at all. If you are not 100% sure about this change, consult with your web host.', 'wp-2fa' ); ?>
</p>
<br/>
<span><?php \esc_html_e( 'Email Address:', 'wp-2fa' ); ?></span> <input type="text" id="custom_from_email_address" name="custom_from_email_address" value="<?php echo \esc_attr( WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ) ); ?>"><br><br>
<span><?php \esc_html_e( 'Display Name:', 'wp-2fa' ); ?></span> <input type="text" id="custom_from_display_name" name="custom_from_display_name" value="<?php echo \esc_attr( WP2FA::get_wp2fa_email_templates( 'custom_from_display_name' ) ); ?>">
</fieldset>
</fieldset>
</td>
</tr>
</tbody>
</table>
<div class="description"><i><?php \esc_html_e( 'Tip: The \'From email\' address should match your website domain. If the "from address" does not match your website domain, the emails may be blocked or marked as spam. If you are not sure about this please consult with your website administrator / developer or ', 'wp-2fa' ); ?><a href="<?php echo \esc_url( 'https://melapress.com/contact/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ); ?>" target="_blank"><?php \esc_html_e( 'contact us', 'wp-2fa' ); ?></a> <?php \esc_html_e( 'for more information.', 'wp-2fa' ); ?></i></div>
<br>
<hr>
<h3><?php \esc_html_e( 'Email delivery test', 'wp-2fa' ); ?></h3>
<p class="description">
<?php \esc_html_e( 'The plugin sends emails with one-time codes, blocked account notifications and more. Use the button below to confirm the plugin can successfully send emails.', 'wp-2fa' ); ?>
</p>
<p>
<button type="button" name="test_email_config_test"
class="button js-button-test-email-trigger"
data-email-id="config_test"
<?php echo WP_Helper::create_data_nonce( 'wp-2fa-email-test-config_test' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php \esc_html_e( 'Test email delivery', 'wp-2fa' ); ?>
</button>
</p>
<br>
<hr>
<?php
}
/**
* Creates the email notification definitions.
*
* @return Email_Template[]
*
* @since 2.0.0
*/
public static function get_email_notification_definitions() {
$backup_codes = new Email_Template(
'user_backup_codes',
\esc_html__( 'User backup codes email', 'wp-2fa' ),
\esc_html__( 'This email can be sent a user once backup codes are generated.', 'wp-2fa' )
);
$backup_codes->set_can_be_toggled( false );
$result = array(
new Email_Template(
'login_code_setup',
\esc_html__( '2FA setup code email', 'wp-2fa' ),
\esc_html__( 'This is the email sent to a user when setting up 2FA via email.', 'wp-2fa' )
),
new Email_Template(
'login_code',
\esc_html__( 'Login code email', 'wp-2fa' ),
\esc_html__( 'This is the email sent to a user when a login code is required.', 'wp-2fa' )
),
new Email_Template(
'account_locked',
\esc_html__( 'User account locked email', 'wp-2fa' ),
\esc_html__( 'This is the email sent to a user upon grace period expiry.', 'wp-2fa' )
),
new Email_Template(
'account_unlocked',
\esc_html__( 'User account unlocked email', 'wp-2fa' ),
\esc_html__( 'This is the email sent to a user when the user\'s account has been unlocked.', 'wp-2fa' )
),
new Email_Template(
'reset_password_code',
\esc_html__( 'User reset password code email', 'wp-2fa' ),
\esc_html__( 'This is the email sent to a user when a password reset is requested.', 'wp-2fa' )
),
$backup_codes,
);
/**
* Add an option for external providers to implement their own email template settings for the settings tab.
*
* @param array $result - The array with all the email templates.
*
* @since 2.0.0
*/
$result = \apply_filters( WP_2FA_PREFIX . 'email_notification_definitions', $result );
if ( count( $result ) > 6 ) {
$result[0]->set_can_be_toggled( false );
$result[1]->set_can_be_toggled( false );
$result[2]->set_can_be_toggled( false );
$result[3]->set_email_content_id( 'user_account_locked' );
$result[4]->set_email_content_id( 'user_account_unlocked' );
$result[5]->set_can_be_toggled( false );
} else {
$result[0]->set_can_be_toggled( false );
$result[1]->set_can_be_toggled( false );
$result[2]->set_email_content_id( 'user_account_locked' );
$result[3]->set_email_content_id( 'user_account_unlocked' );
$result[4]->set_can_be_toggled( false );
}
return $result;
}
/**
* Validate email templates before saving
*
* @since 2.0.0
*
* @SuppressWarnings(PHPMD.ExitExpressions)
*/
public static function validate_and_sanitize() {
// Bail if user doesn't have permissions to be here.
if ( ! \current_user_can( 'manage_options' ) ) {
return;
}
Debugging::log( 'The following settings will be processed (E-mail): ' . "\n" . wp_json_encode( $_POST ) ); // phpcs:ignore
if ( empty( $_POST ) || ! isset( $_POST['_wpnonce'] ) || empty( $_POST['_wpnonce'] ) || ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'email_settings-options' ) && ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'settings-options' ) || ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'email_settings-options' ) && ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'settings-options' ) ) { // phpcs:ignore
die( \esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
}
$output = array();
if ( isset( $_POST['email_from_setting'] ) && 'use-defaults' === $_POST['email_from_setting'] || isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] ) {
$output['email_from_setting'] = \sanitize_text_field( \wp_unslash( $_POST['email_from_setting'] ) );
}
if ( isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_email_address'] ) && empty( $_POST['custom_from_email_address'] ) ) {
\add_settings_error(
WP_2FA_SETTINGS_NAME,
\esc_attr( 'email_from_settings_error' ),
\esc_html__( 'Please provide an email address', 'wp-2fa' ),
'error'
);
$output['custom_from_email_address'] = '';
}
if ( isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_display_name'] ) && empty( $_POST['custom_from_display_name'] ) ) {
\add_settings_error(
WP_2FA_SETTINGS_NAME,
\esc_attr( 'display_name_settings_error' ),
\esc_html__( 'Please provide a display name.', 'wp-2fa' ),
'error'
);
$output['custom_from_email_address'] = '';
}
if ( isset( $_POST['custom_from_email_address'] ) && ! empty( $_POST['custom_from_email_address'] ) ) {
if ( ! filter_var( \wp_unslash( $_POST['custom_from_email_address'] ), FILTER_VALIDATE_EMAIL ) ) {
\add_settings_error(
WP_2FA_SETTINGS_NAME,
\esc_attr( 'email_invalid_settings_error' ),
\esc_html__( 'Please provide a valid email address. Your email address has not been updated.', 'wp-2fa' ),
'error'
);
}
$output['custom_from_email_address'] = \sanitize_email( \wp_unslash( $_POST['custom_from_email_address'] ) );
Settings_Utils::delete_option( 'dismiss_notice_mail_domain' );
}
if ( ! isset( $_POST['email_from_setting'] ) ) {
Settings_Utils::delete_option( 'dismiss_notice_mail_domain' );
}
if ( isset( $_POST['custom_from_display_name'] ) && ! empty( $_POST['custom_from_display_name'] ) ) {
// Check if the string contains HTML/tags.
preg_match( "/<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/", sanitize_text_field( wp_unslash( $_POST['custom_from_display_name'] ) ), $matches );
if ( count( $matches ) > 0 ) {
\add_settings_error(
WP_2FA_SETTINGS_NAME,
\esc_attr( 'display_name_invalid_settings_error' ),
\esc_html__( 'Please only use alphanumeric text. Your display name has not been updated.', 'wp-2fa' ),
'error'
);
} else {
$output['custom_from_display_name'] = \sanitize_text_field( \wp_unslash( $_POST['custom_from_display_name'] ) );
}
}
if ( isset( $_POST['login_code_email_subject'] ) ) {
$output['login_code_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['login_code_email_subject'] ) );
}
if ( isset( $_POST['login_code_email_body'] ) ) {
$output['login_code_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['login_code_email_body'] ) ) );
}
if ( isset( $_POST['login_code_setup_email_subject'] ) ) {
$output['login_code_setup_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['login_code_setup_email_subject'] ) );
}
if ( isset( $_POST['login_code_setup_email_body'] ) ) {
$output['login_code_setup_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['login_code_setup_email_body'] ) ) );
}
if ( isset( $_POST['user_account_locked_email_subject'] ) ) {
$output['user_account_locked_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['user_account_locked_email_subject'] ) );
}
if ( isset( $_POST['user_account_locked_email_body'] ) ) {
$output['user_account_locked_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['user_account_locked_email_body'] ) ) );
}
if ( isset( $_POST['user_account_unlocked_email_subject'] ) ) {
$output['user_account_unlocked_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['user_account_unlocked_email_subject'] ) );
}
if ( isset( $_POST['user_account_unlocked_email_body'] ) ) {
$output['user_account_unlocked_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['user_account_unlocked_email_body'] ) ) );
}
if ( isset( $_POST['reset_password_code_email_body'] ) ) {
$output['reset_password_code_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['reset_password_code_email_body'] ) ) );
}
if ( isset( $_POST['reset_password_code_email_subject'] ) ) {
$output['reset_password_code_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['reset_password_code_email_subject'] ) );
}
$output['send_account_locked_email'] = '';
if ( isset( $_POST['send_account_locked_email'] ) && 'enable_account_locked_email' === $_POST['send_account_locked_email'] ) {
$output['send_account_locked_email'] = \sanitize_text_field( \wp_unslash( $_POST['send_account_locked_email'] ) );
}
$output['send_account_unlocked_email'] = '';
if ( isset( $_POST['send_account_unlocked_email'] ) && 'enable_account_unlocked_email' === $_POST['send_account_unlocked_email'] ) {
$output['send_account_unlocked_email'] = \sanitize_text_field( \wp_unslash( $_POST['send_account_unlocked_email'] ) );
}
$output['send_login_code_email'] = '';
if ( isset( $_POST['send_login_code_email'] ) && 'enable_send_login_code_email' === $_POST['send_login_code_email'] ) {
$output['send_login_code_email'] = \sanitize_text_field( \wp_unslash( $_POST['send_login_code_email'] ) );
}
if ( isset( $_POST['user_backup_codes_email_subject'] ) ) {
$output['user_backup_codes_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['user_backup_codes_email_subject'] ) );
}
if ( isset( $_POST['user_backup_codes_email_body'] ) ) {
$output['user_backup_codes_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['user_backup_codes_email_body'] ) ) );
}
/**
* Filter the values we are about to store in the plugin settings.
*
* @param array $output - The output array with all the data we will store in the settings.
*
* @since 2.0.0
*/
$output = \apply_filters( WP_2FA_PREFIX . 'filter_output_email_template_content', $output );
Debugging::log( 'The following settings are being saved (E-mail): ' . "\n" . \wp_json_encode( $output ) );
// Remove duplicates from settings errors. We do this as this sanitization callback is actually fired twice, so we end up with duplicates when saving the settings for the FIRST TIME only. The issue is not present once the settings are in the DB as the sanitization wont fire again. For details on this core issue - https://core.trac.wordpress.org/ticket/21989.
global $wp_settings_errors;
if ( isset( $wp_settings_errors ) ) {
$errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
$wp_settings_errors = $errors; // phpcs:ignore
}
if ( isset( $output ) ) {
return $output;
} else {
return;
}
}
/**
* Email settings
*
* @return void
*
* @since 2.0.0
*/
private static function email_settings() {
$custom_user_page_id = Settings::check_setting_in_all_roles( 'custom-user-page-id' );
if ( empty( $custom_user_page_id ) ) {
$custom_user_page_id = Settings::check_setting_in_all_roles( 'custom-user-page-url' );
}
$email_template_definitions = self::get_email_notification_definitions();
?>
<h1><?php \esc_html_e( 'Email Templates', 'wp-2fa' ); ?></h1>
<?php foreach ( $email_template_definitions as $email_template ) : ?>
<?php $template_id = $email_template->get_id(); ?>
<h3><?php echo \esc_html( $email_template->get_title() ); ?></h3>
<p class="description"><?php echo $email_template->get_description(); // phpcs:ignore ?></p>
<table class="form-table">
<tbody>
<?php if ( $email_template->can_be_toggled() ) : ?>
<tr>
<th><label for="send_<?php echo \esc_attr( $template_id ); ?>_email"><?php \esc_html_e( 'Send this email', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<input type="checkbox" id="send_<?php echo \esc_attr( $template_id ); ?>_email" name="send_<?php echo \esc_attr( $template_id ); ?>_email" value="enable_<?php echo \esc_attr( $template_id ); ?>_email"
<?php \checked( 'enable_' . $template_id . '_email', WP2FA::get_wp2fa_email_templates( 'send_' . $template_id . '_email' ) ); ?>
>
<label for="send_<?php echo \esc_attr( $template_id ); ?>_email"><?php \esc_html_e( 'Uncheck to disable this message.', 'wp-2fa' ); ?></label>
</fieldset>
</td>
</tr>
<?php endif; ?>
<?php $template_id = $email_template->get_email_content_id(); ?>
<tr>
<th><label for="<?php echo \esc_attr( $template_id ); ?>_email_subject"><?php \esc_html_e( 'Email subject', 'wp-2fa' ); ?></label></th>
<td>
<fieldset>
<input type="text" id="<?php echo \esc_attr( $template_id ); ?>_email_subject" name="<?php echo \esc_attr( $template_id ); ?>_email_subject" class="large-text" value="<?php echo \esc_attr( WP2FA::get_wp2fa_email_templates( $template_id . '_email_subject' ) ); ?>">
</fieldset>
</td>
</tr>
<tr>
<th>
<label for="<?php echo \esc_attr( $template_id ); ?>_email_body"><?php \esc_html_e( 'Email body', 'wp-2fa' ); ?></label>
</br>
<label for="<?php echo \esc_attr( $template_id ); ?>_email_tags" style="font-weight: 400;"><?php \esc_html_e( 'Available template tags:', 'wp-2fa' ); ?></label>
</br>
</br>
<span style="font-weight: 400;">
{site_url}</br>
{site_name}</br>
{grace_period}</br>
{user_login_name}</br>
{user_first_name}</br>
{user_last_name}</br>
{user_display_name}</br>
{login_code}</br>
{user_ip_address}</br>
{backup_codes}
<?php
if ( ! empty( $custom_user_page_id ) ) {
echo '</br>{2fa_settings_page_url}';
}
?>
</span>
</th>
<td>
<fieldset>
<?php
$message = WP2FA::get_wp2fa_email_templates( $template_id . '_email_body' );
$content = $message;
$editor_id = $template_id . '_email_body';
$settings = array(
'media_buttons' => false,
'editor_height' => 200,
);
\wp_editor( $content, $editor_id, $settings );
?>
</fieldset>
<p>
<button type="button" name="test_email_<?php echo \esc_attr( $template_id ); ?>"
class="button js-button-test-email-trigger"
data-email-id="<?php echo \esc_attr( $template_id ); ?>"
<?php echo WP_Helper::create_data_nonce( 'wp-2fa-email-test-' . $template_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php \esc_html_e( 'Send test email', 'wp-2fa' ); ?>
</button>
</p>
</td>
</tr>
</tbody>
</table>
<br>
<hr>
<?php endforeach; ?>
<?php
$additional_content = apply_filters( WP_2FA_PREFIX . 'append_to_email_and_sms_template_settings', '' );
echo $additional_content;
}
}
}
includes/classes/Admin/index.php 0000644 00000000046 15075513060 0012661 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/class-plugin-updated-notice.php 0000644 00000010505 15075513060 0017057 0 ustar 00 <?php
/**
* Responsible for WP2FA update notices.
*
* @package wp2fa
* @subpackage user-utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin;
use WP2FA\Utils\Settings_Utils;
/**
* Plugin_Updated_Notice class with user notification filters
*
* @since 2.7.0
*/
if ( ! class_exists( '\WP2FA\Admin\Plugin_Updated_Notice' ) ) {
/**
* Plugin_Updated_Notice - Class for displaying notices to our users.
*/
class Plugin_Updated_Notice {
/**
* Lets set things up
*
* @since 2.7.0
*/
public static function init() {
add_action( 'admin_init', array( __CLASS__, 'on_plugin_update' ), 10 );
add_action( 'admin_notices', array( __CLASS__, 'plugin_update_banner' ) );
add_action( 'network_admin_notices', array( __CLASS__, 'plugin_update_banner' ) );
add_action( 'wp_ajax_dismiss_update_notice', array( __CLASS__, 'dismiss_update_notice' ) );
}
/**
* The nag content
*
* @since 2.7.0
* @return void
*/
public static function plugin_update_banner() {
$screen = get_current_screen();
$correct_screen = ( 'toplevel_page_wp-2fa-policies-network' === $screen->base || 'toplevel_page_wp-2fa-policies' === $screen->base ) ? true : false;
if ( $correct_screen && Settings_Utils::get_option( 'wp_2fa_update_notice_needed', false ) ) {
/* translators: %s: version number. */
printf( '<div id="wp_2fa_update_notice" class="notice notice-success is-dismissible"><img src="' . esc_url( WP_2FA_URL . 'dist/images/wp-2fa-square.png' ) . '"><p><strong>' . esc_html__( 'Thank you for updating WP 2FA.', 'wp-2fa' ) . '</strong></p><p>' . esc_html__( 'This is version %s. Check out the release notes to see what is new and improved in this update.', 'wp-2fa' ) . '</p><a href="https://melapress.com/wordpress-2fa/releases/" target="_blank" class="button button-primary dismiss_update_notice" data-dismiss-nonce="%2s">' . esc_html__( 'Release notes', 'wp-2fa' ) . '</a></p></div>', WP_2FA_VERSION, wp_create_nonce( 'wp_2fa_dismiss_update_notice_nonce' ) );
?>
<script type="text/javascript">
//<![CDATA[
jQuery(document).ready(function( $ ) {
jQuery( 'body' ).on( 'click', 'a.dismiss_update_notice, #wp_2fa_update_notice .notice-dismiss', function ( e ) {
var nonce = jQuery( '#wp_2fa_update_notice [data-dismiss-nonce]' ).attr( 'data-dismiss-nonce' );
jQuery.ajax({
type: 'POST',
url: '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>',
async: true,
data: {
action: 'dismiss_update_notice',
nonce : nonce,
},
success: function ( result ) {
jQuery( '#wp_2fa_update_notice' ).slideUp( 300 );
}
});
});
});
//]]>
</script>
<style>
#wp_2fa_update_notice {
border: 2px solid #0f5cf2
}
#wp_2fa_update_notice .button-primary {
background: #0f5cf2;
border-color: #0f5cf2;
}
#wp_2fa_update_notice img {
float: left;
max-width: 100px;
margin: 10px 12px 10px 0;
}
</style>
<?php
}
}
/**
* Redirects user to admin on plugin update.
*
* @since 2.7.0
* @return void
*/
public static function on_plugin_update() {
if ( Settings_Utils::get_option( 'wp_2fa_update_redirection_needed', false ) ) {
delete_site_option( 'wp_2fa_update_redirection_needed' );
update_site_option( 'wp_2fa_update_notice_needed', true );
$args = array(
'page' => 'wp-2fa-policies',
);
$url = add_query_arg( $args, network_admin_url( 'admin.php' ) );
wp_safe_redirect( $url );
exit;
}
}
/**
* Handle notice dismissal.
*
* @since 2.7.0
* @return void
*/
public static function dismiss_update_notice() {
// Grab POSTed data.
$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : false;
// Check nonce.
if ( ! current_user_can( 'manage_options' ) || empty( $nonce ) || ! $nonce || ! wp_verify_nonce( $nonce, 'wp_2fa_dismiss_update_notice_nonce' ) ) {
wp_send_json_error( esc_html__( 'Nonce Verification Failed.', 'wp-2fa' ) );
}
delete_site_option( 'wp_2fa_update_notice_needed' );
wp_send_json_success( esc_html__( 'Complete.', 'wp-2fa' ) );
}
}
}
includes/classes/Admin/class-user-profile.php 0000644 00000100045 15075513060 0015271 0 ustar 00 <?php
/**
* Responsible for WP2FA user's profile settings.
*
* @package wp2fa
* @subpackage user-utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin;
use WP2FA\WP2FA;
use WP2FA\Methods\TOTP;
use WP2FA\Methods\Email;
use WP2FA\Utils\User_Utils;
use WP2FA\Extensions_Loader;
use WP2FA\Methods\Backup_Codes;
use WP2FA\Utils\Generate_Modal;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Authenticator\Open_SSL;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Freemius\User_Licensing;
use WP2FA\Admin\Views\Wizard_Steps;
use WP2FA\Admin\Controllers\Methods;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Authenticator\Authentication;
use WP2FA\Extensions\OutOfBand\Out_Of_Band;
/**
* User_Profile class responsible for the profile page operations
*
* @since 2.4.0
*/
if ( ! class_exists( '\WP2FA\Admin\User_Profile' ) ) {
/**
* User_Profile - Class for handling user things such as profile settings and admin list views.
*
* @since 2.7.0
*/
class User_Profile {
/**
* Add our buttons to the user profile editing screen.
*
* @param object $user User data.
* @param array $additional_args - Array with extra parameters for the method.
*
* @since 2.7.0
*/
public static function user_2fa_options( $user, $additional_args = array() ) {
if ( isset( $_GET['user_id'] ) ) { // phpcs:ignore
$user_id = (int) $_GET['user_id']; // phpcs:ignore
$user = \get_user_by( 'id', $user_id );
} else {
// Get current user, we're going to need this regardless.
$user = \wp_get_current_user();
}
if ( ! is_a( $user, '\WP_User' ) ) {
return;
}
// Ensure we have something in the settings.
if ( empty( Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) ) {
return;
}
$show_preamble = true;
if ( isset( $additional_args['show_preamble'] ) ) {
$show_preamble = \filter_var( $additional_args['show_preamble'], FILTER_VALIDATE_BOOLEAN );
}
$user_type = User_Utils::determine_user_2fa_status( $user );
$form_output = '';
$form_content = '';
$description = WP2FA::get_wp2fa_white_label_setting( 'user-profile-form-preamble-desc', true );
$show_form_table = true;
$page_url = ( WP_Helper::is_multisite() ) ? 'index.php' : 'options-general.php';
// Orphan user (a user with no role or capabilities).
if ( in_array( 'orphan_user', $user_type, true ) ) {
// We want to use the same form/buttons used in the shortcode.
$additional_args['is_shortcode'] = true;
// Create useful message for admin.
if ( User_Utils::in_array_all( array( 'user_needs_to_setup_2fa', 'can_manage_options' ), $user_type ) ) {
$description = \esc_html__( 'This user is required to setup 2FA but has not yet done so.', 'wp-2fa' );
}
if ( User_Utils::in_array_all( array( 'user_is_excluded', 'can_manage_options' ), $user_type ) ) {
$description = \esc_html__( 'This user is excluded from configuring 2FA.', 'wp-2fa' );
}
}
// Excluded user.
if ( in_array( 'user_is_excluded', $user_type, true ) ) {
return;
}
// A user viewing their own profile AND has a 2FA method configured.
if ( User_Utils::in_array_all( array( 'viewing_own_profile' ), $user_type ) ) {
if (
User_Utils::in_array_all( array( 'has_enabled_methods' ), $user_type ) ||
User_Utils::in_array_all( array( 'no_required_has_enabled' ), $user_type )
) {
if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
$form_content = '';
/**
* Gives the ability to remove the user's settings.
*
* @param bool - The status of the settings.
*
* @since 2.2.2
*/
$show_enable2fa = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting', true );
/**
* Gives the ability to change the user profile description message.
*
* @param bool - The status of the settings.
*
* @since 2.4.0
*/
$description = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting_description', $description );
$styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
if ( $show_enable2fa ) {
$form_content = '<a href="#" class="button button-primary remove-2fa ' . \esc_attr( $styling_class ) . '" data-open-configure-2fa-wizard>' . \esc_html__( 'Change 2FA settings', 'wp-2fa' ) . '</a>';
}
if ( self::can_user_remove_2fa( $user->ID ) ) {
$form_content .= '<a href="#" class="button button-primary remove-2fa ' . \esc_attr( $styling_class ) . '" onclick="MicroModal.show(\'confirm-remove-2fa\');">' . \esc_html__( 'Remove 2FA', 'wp-2fa' ) . '</a>';
}
$form_content .= '</td><tr><th class="backup-methods-label">';
$backup_codes_desc = '';
if ( Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) ) {
$codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
if ( $codes_remaining > 0 ) {
$backup_codes_desc = '<span class="description mt-5px">' . \esc_attr( (int) $codes_remaining ) . ' ' . \esc_html__( 'unused backup codes remaining.', 'wp-2fa' ) . '</span>';
} elseif ( 0 === $codes_remaining ) {
$backup_codes_desc = '<a class="learn_more_link" href="https://melapress.com/2fa-backup-codes/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'Learn more about backup codes', 'wp-2fa' ) . '</a>';
}
if ( ! empty( $backup_codes_desc ) ) {
$backup_codes_desc = Wizard_Steps::get_backup_codes_link() . $backup_codes_desc;
}
}
/**
* Add an option for external providers to add their own user form buttons.
*
* @since 2.0.0
*/
$backup_codes_desc = apply_filters( WP_2FA_PREFIX . 'additional_form_buttons', $backup_codes_desc );
if ( ! empty( $backup_codes_desc ) ) {
$form_content .= Wizard_Steps::get_generate_codes_label() . $backup_codes_desc;
}
$form_content .= '</th></tr>';
}
}
$show_if_user_is_not_in = array(
'user_is_excluded',
'has_enabled_methods',
'no_required_has_enabled',
);
// User viewing own profile and needs to enable 2FA.
if (
User_Utils::in_array_all( array( 'user_needs_to_setup_2fa' ), $user_type ) ||
User_Utils::role_is_not( $show_if_user_is_not_in, $user_type )
) {
$first_time_setup_url = Settings::get_setup_page_link();
/**
* Gives the ability to remove the user's settings.
*
* @param bool - The status of the settings.
*
* @since 2.2.2
*/
$show_enable2fa = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting', true );
/**
* Gives the ability to change the user profile description message.
*
* @param bool - The status of the settings.
*
* @since 2.4.0
*/
$description = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting_description', $description );
$styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
if ( $show_enable2fa ) {
if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
$form_content .= '<a href="#" class="button button-primary ' . \esc_attr( $styling_class ) . '" data-open-configure-2fa-wizard>' . \esc_html__( 'Configure 2FA', 'wp-2fa' ) . '</a>';
}
if ( empty( $additional_args ) ) {
$form_content .= '<a href="' . \esc_url( $first_time_setup_url ) . '" class="button button-primary ' . \esc_attr( $styling_class ) . '">' . \esc_html__( 'Configure Two-factor authentication (2FA)', 'wp-2fa' ) . '</a>';
}
}
}
}
// Admin viewing users profile AND user has a configured 2FA method.
if ( User_Utils::in_array_all( array( 'can_manage_options', 'has_enabled_methods' ), $user_type ) && ! in_array( 'viewing_own_profile', $user_type, true ) ) {
$description = \esc_html__( 'The user has already configured 2FA. When you reset the user\'s current 2FA configuration, the user can log back in with just the username and password.', 'wp-2fa' );
$remove_users_2fa_url = add_query_arg(
array(
'action' => 'remove_user_2fa',
'user_id' => $user->ID,
'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-remove-user-2fa-nonce' ),
'admin_reset' => 'yes',
),
admin_url( 'user-edit.php' )
);
$form_content .= '<a href="' . \esc_url( $remove_users_2fa_url ) . '" class="button button-primary">' . \esc_html__( 'Reset 2FA configuration', 'wp-2fa' ) . '</a>';
}
// Admin viewing users profile AND users grace period has expired.
if ( User_Utils::in_array_all( array( 'can_manage_options', 'grace_has_expired' ), $user_type ) ) {
$unlock_user_url = add_query_arg(
array(
'action' => 'unlock_account',
'user_id' => $user->ID,
'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-unlock-account-nonce' ),
),
admin_url( 'user-edit.php' )
);
$form_content .= '<a href="' . \esc_url( $unlock_user_url ) . '" class="button button-primary">' . \esc_html__( 'Unlock user and reset the grace period', 'wp-2fa' ) . '</a>';
}
if ( $show_preamble ) {
$form_output .= '<h2>' . WP2FA::get_wp2fa_white_label_setting( 'user-profile-form-preamble-title', true ) . '</h2>';
if ( $description ) {
$form_output .= '<p class="description">' . $description . '</p>';
}
}
/**
* Gives the ability to add more content to the profile page.
*
* @param string $form_content - The parsed HTML of the form.
*/
$form_content = apply_filters( WP_2FA_PREFIX . 'append_to_profile_form_content', $form_content );
if ( $show_form_table && ! empty( $form_content ) ) {
$enabled_methods = User_Helper::get_enabled_method_for_user( $user );
$primary_label = ( isset( $enabled_methods ) && ! empty( $enabled_methods ) ) ? Settings::get_providers_translate_names()[ $enabled_methods ] : \esc_html__( 'No enabled primary method', 'wp-2fa' );
$enabled_backup_methods = User_Helper::get_enabled_backup_methods_for_user( $user );
$backup_methods_enabled = \esc_html__( 'No enabled backup methods', 'wp-2fa' );
if ( isset( $enabled_backup_methods ) && ! empty( $enabled_backup_methods ) ) {
$backup_methods_enabled = \implode( ', ', $enabled_backup_methods );
}
$show_enabled = true;
if ( isset( $additional_args ) && ! empty( $additional_args ) && isset( $additional_args['options'] ) && ! empty( $additional_args['options'] ) ) {
if ( isset( $additional_args['options']['do_not_show_enabled'] ) && 'false' !== $additional_args['options']['do_not_show_enabled'] ) {
$show_enabled = false;
}
}
if ( $show_enabled ) {
$form_output .= '<h3>' . \esc_html__( 'Currently configured:', 'wp-2fa' ) . '</h3>';
$form_output .= '
<table id="2fa-currently-configured-methods" class="form-table wp-2fa-user-profile-form" role="presentation">
<tbody>
<tr>
<th><label>' . \esc_html__( 'Primary method:', 'wp-2fa' ) . '</label></th>
<td>
' . $primary_label . '
</td>
</tr>';
$form_output .= '
<tr>
<th><label>' . \esc_html__( 'Secondary method(s):', 'wp-2fa' ) . '</label></th>
<td>
' . $backup_methods_enabled . '
</td>
</tr>';
$form_output .= '
</tbody>
</table>';
}
$form_output .= '<h3>' . \esc_html__( '2FA configuration:', 'wp-2fa' ) . '</h3>';
if ( User_Utils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) && isset( $enabled_methods ) && TOTP::METHOD_NAME === $enabled_methods ) {
$form_output .= '
<table id="2fa-configuration-options" class="form-table wp-2fa-user-profile-form remove-tr-padding" role="presentation">
<tbody>
<tr>
<th><label>' . Settings::get_providers_translate_names()[ $enabled_methods ] . '</label></th>
<td>
<details>
<summary class="qr-btn">' . \esc_html__( 'Show QR code', 'wp-2fa' ) . '</summary>
<p><img class="qr-code" src="' . ( TOTP::get_qr_code() ) . '" /></p>
<div class="app-key-wrapper">
<input type="text" id="app-key-input" readonly value="' . \esc_html( TOTP::get_totp_decrypted() ) . '" class="app-key">
' .
( ( is_ssl() ) ?
'<span class="click-to-copy">' . \esc_html__( 'COPY', 'wp-2fa' ) . '</span>' : '' ) . '
</div>
</details>
</td>
</tr>
</tbody>
</table>';
}
$form_output .= '
<table id="2fa-user-global-configuration" class="form-table wp-2fa-user-profile-form" role="presentation">
<tbody>
<tr>
<th><label>' . \esc_html__( '2FA Setup:', 'wp-2fa' ) . '</label></th>
<td>
' . $form_content . '
</td>
</tr>
</tbody>
</table>';
if ( ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) || User_Helper::get_user_enforced_instantly( $user ) ) { // phpcs:ignore
$form_output .= '
<script>
window.addEventListener("load", function() {
wp2fa_fireWizard();
});
</script>
';
}
}
echo $form_output; // phpcs:ignore
self::generate_inline_modals( $user_type );
}
/**
* Responsible for the building of all the modals.
*
* @param array $user_type - The WP user type.
*
* @return void
*
* @since 2.7.0
*/
public static function generate_inline_modals( $user_type = array() ) {
ob_start();
$user = \wp_get_current_user();
$styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
if ( User_Utils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( 'no_required_not_enabled', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( User_Helper::USER_UNDETERMINED_STATUS, 'viewing_own_profile' ), $user_type ) ) { ?>
<div>
<div class="wp2fa-modal micromodal-slide <?php echo \esc_attr( $styling_class ); ?>" id="configure-2fa" aria-hidden="true">
<div class="modal__overlay" tabindex="-1">
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
<?php
echo Generate_Modal::generate_modal( // phpcs:ignore
'notify-users',
__( 'Are you sure?', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
__( 'Any unsaved changes will be lost!', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
array(
'<button class="button wp-2fa-button-primary button-primary button-confirm" aria-label="Close this dialog window and the wizard">' . \esc_html__( 'Yes', 'wp-2fa' ) . '</button>',
'<button class="button wp-2fa-button-secondary button-secondary button-decline" data-micromodal-close aria-label="Close this dialog window">' . \esc_html__( 'No', 'wp-2fa' ) . '</button>',
),
'',
'430px'
);
?>
<button class="modal__close modal_cancel" aria-label="Close modal"></button>
<main class="modal__content wp2fa-form-styles" id="modal-1-content">
<?php
$logo_url = WP2FA::get_wp2fa_white_label_setting( 'logo-code-page', false );
$logo_section = ( $logo_url ) ? '<p class="modal-logo-wrapper"><img style="max-height: 60px;margin: 0 auto 30px;" src="' . \esc_url( $logo_url ) . '" /></p>' : '';
$enable_logo = WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_logo', false );
if ( $enable_logo ) {
echo $logo_section; // phpcs:ignore */
}
if ( User_Utils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( 'no_required_not_enabled', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( User_Helper::USER_UNDETERMINED_STATUS, 'viewing_own_profile' ), $user_type ) ) {
$available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
$optional_welcome = WP2FA::get_wp2fa_white_label_setting( 'welcome', false );
$enable_welcome = WP2FA::get_wp2fa_white_label_setting( 'enable_welcome', false );
$intro_text = '';
if ( count( $available_methods[ User_Helper::get_user_role( $user ) ] ) > 1 ) {
$intro_text = WP2FA::replace_wizard_strings( WP2FA::get_wp2fa_white_label_setting( 'method_selection', true ), $user );
} elseif ( 1 === count( $available_methods[ User_Helper::get_user_role( $user ) ] ) ) {
$intro_text = WP2FA::get_wp2fa_white_label_setting( 'method_selection_single', true );
} else {
$intro_text = '<h3>' . __( 'No available 2FA methods set', 'wp-2fa' ) . '</h3><p>' . __( 'Ask your administrator to enable 2FA methods', 'wp-2fa' ) . '</p>';
}
if ( ! empty( $optional_welcome ) && $enable_welcome ) {
Wizard_Steps::optional_user_welcome_step();
}
?>
<div class="wizard-step <?php echo ( empty( $optional_welcome ) ) ? 'active' : ''; ?>" id="choose-2fa-method">
<div class="mb-20"><?php echo \wp_kses_post( $intro_text ); ?></div>
<fieldset class="radio-cells">
<?php
/**
* Adds an option for external providers to add their own 2fa methods options. And sorts them (our logic).
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'methods_options' );
?>
</fieldset>
<br>
<?php
if ( 0 !== count( $available_methods[ User_Helper::get_user_role( $user ) ] ) ) {
?>
<a href="#" class="button wp-2fa-button-primary button-primary 2fa-choose-method" data-name="next_step_setting_modal_wizard" data-next-step><?php \esc_html_e( 'Next Step', 'wp-2fa' ); ?></a>
<?php
}
?>
<button class="button wp-2fa-button-secondary button-secondary" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
</div>
<?php } ?>
<?php if ( User_Utils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) ) { ?>
<div class="wizard-step active">
<fieldset class="radio-cells max-3">
<?php
/**
* Add an option for external providers to add their own reconfigure methods options.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'methods_reconfigure_options' );
?>
</fieldset>
</div>
<?php } ?>
<?php Wizard_Steps::show_modal_methods(); ?>
<?php
$backup_methods = Settings::get_enabled_backup_methods_for_user_role( $user );
if ( count( $backup_methods ) > 1 ) {
Wizard_Steps::choose_backup_method();
}
/**
* Add an option for external providers to add their own wizard steps.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'additional_settings_steps' );
// Create a nonce for use in ajax call to generate codes.
if ( Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) ) {
?>
<div class="wizard-step" id="2fa-wizard-config-backup-codes">
<?php Wizard_Steps::backup_codes_configure(); ?>
<?php Wizard_Steps::generated_backup_codes(); ?>
</div>
<?php } else { ?>
<div class="wizard-step" id="2fa-wizard-config-backup-codes">
<?php Wizard_Steps::congratulations_step(); ?>
</div>
<?php } ?>
</main>
</div>
</div>
</div>
</div>
<?php } ?>
<?php
/**
* Add an option for external providers to add their own 2fa methods options.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'methods_wizards' );
?>
<?php if ( Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) ) { ?>
<div>
<div class="wp2fa-modal micromodal-slide <?php echo \esc_attr( $styling_class ); ?>" id="configure-2fa-backup-codes" aria-hidden="true">
<div class="modal__overlay" tabindex="-1">
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
<button class="modal__close modal_cancel" aria-label="Close modal" data-close-2fa-modal></button>
<main class="modal__content wp2fa-form-styles" id="modal-1-content">
<?php Wizard_Steps::generated_backup_codes( true ); ?>
</main>
</div>
</div>
</div>
</div>
<?php } ?>
<div>
<?php
if ( self::can_user_remove_2fa( $user->ID ) ) :
echo Generate_Modal::generate_modal( // phpcs:ignore
'confirm-remove-2fa',
__( 'Remove 2FA?', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
__( 'Are you sure you want to remove two-factor authentication and lower the security of your user account?', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
array(
'<a href="#" class="button wp-2fa-button-primary" data-trigger-remove-2fa data-user-id="' . \esc_attr( $user->ID ) . '" ' . WP_Helper::create_data_nonce( 'wp-2fa-remove-user-2fa-nonce' ) . '>' . \esc_html__( 'Yes', 'wp-2fa' ) . '</a>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'<button class="modal__btn wp-2fa-button-secondary button" data-close-2fa-modal aria-label="Close this dialog window">' . \esc_html__( 'No', 'wp-2fa' ) . '</button>',
)
);
endif;
?>
</div>
<?php
$output = ob_get_contents();
ob_end_clean();
echo $output; // phpcs:ignore
}
/**
* Produces the 2FA configuration form for network users, or any user with no roles.
*
* @param string $is_shortcode - Current logic expects that to be set always.
* @param boolean $show_preamble - Show / hide preamble.
* @param array $options - Array with additional options.
*
* @return void
*
* @since 2.7.0
*/
public static function inline_2fa_profile_form( $is_shortcode = 'true', $show_preamble = true, array $options = array() ) {
if ( isset( $_GET['user_id'] ) ) { // phpcs:ignore
$user_id = (int) $_GET['user_id']; // phpcs:ignore
$user = \get_user_by( 'id', $user_id );
} else {
$user = \wp_get_current_user();
}
// Get current user, we going to need this regardless.
$current_user = \wp_get_current_user();
if ( \is_multisite() ) {
if ( '' === trim( (string) \WP2FA\Admin\Helpers\User_Helper::get_user_role( $user ) ) ) {
return;
}
}
// Bail if we still dont have an object.
if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
return;
}
$additional_args = array(
'is_shortcode' => $is_shortcode,
'show_preamble' => $show_preamble,
'options' => $options,
);
self::user_2fa_options( $user, $additional_args );
}
/**
* Add custom unlock account link to user edit admin list.
*
* @param string $actions Default actions.
* @param object $user_object User data.
* @return string Appended actions.
*
* @since 2.7.0
*/
public static function user_2fa_row_actions( $actions, $user_object ) {
$nonce = wp_create_nonce( 'wp-2fa-unlock-account-nonce' );
$grace_period_expired = User_Helper::get_grace_period( $user_object );
$url = add_query_arg(
array(
'action' => 'unlock_account',
'user_id' => $user_object->ID,
'wp_2fa_nonce' => $nonce,
),
admin_url( 'users.php' )
);
if ( $grace_period_expired ) {
$actions['edit_badges'] = '<a href="' . \esc_url( $url ) . '">' . \esc_html__( 'Unlock user', 'wp-2fa' ) . '</a>';
}
return $actions;
}
/**
* Save user profile information.
*
* @param array $input - The array with values to process.
*
* @return void
*
* @since 2.7.0
*/
public static function save_user_2fa_options( $input ) {
// Ensure we have the inputs we want before we process.
// To avoid causing issues with the rest of the user profile.
if ( ! is_array( $input ) ) {
return;
}
// Assign the input to post, in case we are dealing with saving the data from another page.
if ( isset( $input ) ) {
$_POST = $input;
}
// Grab current user.
$user = wp_get_current_user();
// phpcs:disable
// Grab authcode and ensure its a number.
if ( isset( $_POST['wp-2fa-totp-authcode'] ) ) {
$_POST['wp-2fa-totp-authcode'] = (int) $_POST['wp-2fa-totp-authcode'];
}
if ( ( ! isset( $_POST['custom-email-address'] ) || isset( $_POST['custom-email-address'] ) && empty( $_POST['custom-email-address'] ) ) &&
( ! isset( $_POST['custom-oob-email-address'] ) || isset( $_POST['custom-oob-email-address'] ) && empty( $_POST['custom-oob-email-address'] ) ) ) {
if ( isset( $_POST['email'] ) ) {
User_Helper::set_nominated_email_for_user( $_POST['email'], $user );
} elseif ( isset( $_POST['wp_2fa_email_address'] ) && isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
User_Helper::set_nominated_email_for_user( $_POST['wp_2fa_email_address'], $user );
} elseif ( isset( $_POST['wp_2fa_email_oob_address'] ) && isset( $_POST['wp-2fa-oob-authcode'] ) && ! empty( $_POST['wp-2fa-oob-authcode'] ) ) {
if ( 'use_custom_email' !== $_POST['wp_2fa_email_oob_address'] ) {
User_Helper::set_nominated_email_for_user( $_POST['wp_2fa_email_oob_address'], $user );
}
}
} elseif ( isset( $_POST['custom-email-address'] ) && ! empty( $_POST['custom-email-address'] ) ) {
User_Helper::set_nominated_email_for_user( $_POST['custom-email-address'], $user );
} elseif ( isset( $_POST['custom-oob-email-address'] ) && ! empty( $_POST['custom-oob-email-address'] ) ) {
User_Helper::set_nominated_email_for_user( $_POST['custom-oob-email-address'], $user );
}
// Check its one of our options.
if ( ( isset( $_POST['wp_2fa_enabled_methods'] ) && TOTP::METHOD_NAME === $_POST['wp_2fa_enabled_methods'] ) ||
( isset( $_POST['wp_2fa_enabled_methods'] ) && Email::METHOD_NAME === $_POST['wp_2fa_enabled_methods'] ) ||
( isset( $_POST['wp_2fa_enabled_methods'] ) && ( class_exists( '\WP2FA\Extensions\OutOfBand\Out_Of_Band', false ) && Out_Of_Band::METHOD_NAME === $_POST['wp_2fa_enabled_methods'] ) ) ) {
User_Helper::set_enabled_method_for_user(sanitize_text_field( wp_unslash( $_POST['wp_2fa_enabled_methods'] ) ), $user);
self::delete_expire_and_enforced_keys( $user->ID );
User_Helper::set_user_status( $user );
}
if ( isset( $_POST['wp-2fa-email-authcode'] ) && ! empty( $_POST['wp-2fa-email-authcode'] ) ) {
User_Helper::set_enabled_method_for_user( Email::METHOD_NAME, $user );
self::delete_expire_and_enforced_keys( $user->ID );
User_Helper::set_user_status( $user );
}
if ( isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
$totp_key = $_POST['wp-2fa-totp-key'];
if ( Authentication::is_valid_key( $totp_key ) ) {
if ( Open_SSL::is_ssl_available() ) {
$totp_key = Open_SSL::SECRET_KEY_PREFIX . Open_SSL::encrypt( $totp_key );
}
TOTP::set_user_method( $user, $totp_key );
}
}
// phpcs:enable
}
/**
* Utility function to remove user expiry and enforced data.
*
* @param int $user_id User id to process.
*
* @since 2.7.0
*/
public static function delete_expire_and_enforced_keys( $user_id ) {
User_Helper::remove_user_expiry_date( $user_id );
User_Helper::remove_user_enforced_instantly( $user_id );
User_Helper::remove_grace_period( $user_id );
}
/**
* Validate a user's code when setting up 2fa via the inline form.
*
* @return void
*
* @since 2.7.0
*/
public static function validate_authcode_via_ajax() {
check_ajax_referer( 'wp-2fa-validate-authcode' );
if ( isset( $_POST['form'] ) ) {
$input = wp_unslash( $_POST['form'] ); // phpcs:ignore
} else {
wp_send_json_error(
array(
'error' => \esc_html__( 'No form', 'wp-2fa' ),
)
);
}
$user = wp_get_current_user();
$our_errors = '';
// Grab key from the $_POST.
if ( isset( $input['wp-2fa-totp-key'] ) ) {
$current_key = sanitize_text_field( wp_unslash( $input['wp-2fa-totp-key'] ) );
}
// Grab authcode and ensure its a number.
if ( isset( $input['wp-2fa-totp-authcode'] ) ) {
$input['wp-2fa-totp-authcode'] = (int) $input['wp-2fa-totp-authcode'];
}
// Check if we are dealing with totp or email, if totp validate and store a new secret key.
if ( ! empty( $input['wp-2fa-totp-authcode'] ) && ! empty( $current_key ) ) {
if ( Authentication::is_valid_key( $current_key ) || ! is_numeric( $input['wp-2fa-totp-authcode'] ) ) {
if ( ! Authentication::is_valid_authcode( $current_key, sanitize_text_field( wp_unslash( $input['wp-2fa-totp-authcode'] ) ) ) ) {
$our_errors = \esc_html__( 'Invalid Two Factor Authentication code.', 'wp-2fa' );
}
} else {
$our_errors = \esc_html__( 'Invalid Two Factor Authentication secret key.', 'wp-2fa' );
}
// If its not totp, is it email.
} elseif ( ! empty( $input['wp-2fa-email-authcode'] ) ) {
if ( ! Authentication::validate_token( $user, sanitize_text_field( wp_unslash( $input['wp-2fa-email-authcode'] ) ) ) ) {
$our_errors = \esc_html__( 'Invalid Email Authentication code.', 'wp-2fa' );
}
} else {
$our_errors = \esc_html__( 'Please enter the code to finalize the 2FA setup.', 'wp-2fa' );
}
if ( ! empty( $our_errors ) ) {
// Send the response.
wp_send_json_error(
array(
'error' => $our_errors,
)
);
} else {
self::save_user_2fa_options( $input );
// Send the response.
wp_send_json_success();
}
wp_send_json_error(
array(
'error' => \esc_html__( 'Error processing form', 'wp-2fa' ),
)
);
}
/**
* Checks the user for remove 2FA capabilities.
*
* @param int $user_id User ID.
*
* @return bool True if the user can remove 2FA from their account.
*
* @since 2.7.0
*/
public static function can_user_remove_2fa( $user_id ) {
// check the "Hide the Remove 2FA button" setting.
if ( Settings::get_role_or_default_setting( 'hide_remove_button', $user_id ) ) {
return false;
}
// check grace period policy.
$grace_policy = Settings::get_role_or_default_setting( 'grace-policy', $user_id );
if ( 'no-grace-period' === $grace_policy ) {
// we only need to run further checks to find out if the 2FA is enforced for the user in question if there
// is no grace period.
$enforcement_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
if ( 'all-users' === $enforcement_policy ) {
// enforced for all users, target user is definitely included.
return false;
}
if ( 'certain-roles-only' === $enforcement_policy && ! User_Helper::is_enforced( $user_id ) ) {
// Users specific role is not enforced, allow removal.
return true;
}
if ( 'do-not-enforce' !== $enforcement_policy ) {
// one of possible enforcement options is set, check the target user.
return User_Helper::is_enforced( $user_id );
}
}
return true;
}
/**
* Add script to admin footer to allow for nags to be dismissed from all admin pages.
*
* @return void
*
* @since 2.7.0
*/
public static function dismiss_nag_notice() {
?>
<script type="text/javascript">
jQuery( document ).on( 'click', '.dismiss-user-configure-nag', function() {
const thisNotice = jQuery( this ).closest( '.notice' );
jQuery.ajax( {
url: '<?php echo admin_url( 'admin-ajax.php' ); // phpcs:ignore ?>',
data: {
action: 'dismiss_nag'
},
complete: function() {
jQuery( thisNotice ).slideUp();
},
} );
} );
</script>
<?php
}
}
}
includes/classes/Admin/class-user-registered.php 0000644 00000002421 15075513060 0015765 0 ustar 00 <?php
/**
* Responsible for WP2FA user's grace periods.
*
* @package wp2fa
* @subpackage user-utils
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin;
use WP2FA\Admin\Helpers\User_Helper;
if ( ! class_exists( '\WP2FA\Admin\User_Registered' ) ) {
/**
* User_Profile - Class for handling user things such as profile settings and admin list views.
*/
class User_Registered {
/**
* Apply 2FA Grace period
*
* @param int $user_id User id.
*
* @return void
*/
public static function apply_2fa_grace_period( $user_id ) {
if ( User_Helper::is_user_method_in_role_enabled_methods( $user_id ) ) {
return;
} else {
User_Helper::remove_enabled_method_for_user( $user_id );
User_Helper::remove_global_settings_hash_for_user( $user_id );
}
}
/**
* Checks the user on role change.
*
* @param integer $user_id - The ID of the user.
* @param string $role - The user role.
* @param array $old_roles - Old roles for the user.
*
* @return void
*/
public static function check_user_upon_role_change( $user_id, $role, $old_roles ) {
self::apply_2fa_grace_period( $user_id );
}
}
}
includes/classes/Admin/class-settings-page.php 0000644 00000042174 15075513060 0015437 0 ustar 00 <?php
/**
* Settings rendering class.
*
* @package wp2fa
* @subpackage settings
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin;
use WP2FA\WP2FA;
use WP2FA\Admin\SettingsPages\{
Settings_Page_Policies,
Settings_Page_General,
Settings_Page_Email
};
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Utils\Settings_Utils;
/**
* Class for handling settings
*/
if ( ! class_exists( '\WP2FA\Admin\Settings_Page' ) ) {
/**
* Class for handling settings
*/
class Settings_Page {
const TOP_MENU_SLUG = 'wp-2fa-policies';
/**
* Create admin menu entry and settings page
*/
public static function create_settings_admin_menu() {
// Create admin menu item.
\add_menu_page(
\esc_html__( 'WP 2FA', 'wp-2fa' ),
\esc_html__( 'WP 2FA', 'wp-2fa' ),
'manage_options',
self::TOP_MENU_SLUG,
null,
'data:image/svg+xml;base64,' . base64_encode( file_get_contents( WP_2FA_PATH . 'dist/images/wp-2fa-white-icon20x28.svg' ) ), // phpcs:ignore
81
);
\add_submenu_page(
self::TOP_MENU_SLUG,
\esc_html__( '2FA Policies', 'wp-2fa' ),
\esc_html__( '2FA Policies', 'wp-2fa' ),
'manage_options',
self::TOP_MENU_SLUG,
array( \WP2FA\Admin\SettingsPages\Settings_Page_Policies::class, 'render' ),
1
);
\add_submenu_page(
self::TOP_MENU_SLUG,
\esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
\esc_html__( 'Settings', 'wp-2fa' ),
'manage_options',
'wp-2fa-settings',
array( \WP2FA\Admin\SettingsPages\Settings_Page_Render::class, 'render' ),
2
);
// Register our policy settings.
\register_setting(
WP_2FA_POLICY_SETTINGS_NAME,
WP_2FA_POLICY_SETTINGS_NAME,
array( \WP2FA\Admin\SettingsPages\Settings_Page_Policies::class, 'validate_and_sanitize' )
);
// Register our white label settings.
\register_setting(
WP_2FA_WHITE_LABEL_SETTINGS_NAME,
WP_2FA_WHITE_LABEL_SETTINGS_NAME,
array( \WP2FA\Admin\SettingsPages\Settings_Page_White_Label::class, 'validate_and_sanitize' )
);
// Register our settings page.
\register_setting(
WP_2FA_SETTINGS_NAME,
WP_2FA_SETTINGS_NAME,
array( \WP2FA\Admin\SettingsPages\Settings_Page_General::class, 'validate_and_sanitize' )
);
\register_setting(
WP_2FA_EMAIL_SETTINGS_NAME,
WP_2FA_EMAIL_SETTINGS_NAME,
array( \WP2FA\Admin\SettingsPages\Settings_Page_Email::class, 'validate_and_sanitize' )
);
/**
* Fires after the main menu settings are registered.
*
* @param string - The menu slug.
* @param bool - Is that multisite install or not.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'after_admin_menu_created', self::TOP_MENU_SLUG, false );
\add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( __CLASS__, 'check_email' ) );
}
/**
* Create admin menu entry and settings page
*/
public static function create_settings_admin_menu_multisite() {
// Create admin menu item.
\add_menu_page(
\esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
\esc_html__( 'WP 2FA', 'wp-2fa' ),
'manage_options',
self::TOP_MENU_SLUG,
null,
'data:image/svg+xml;base64,' . base64_encode( file_get_contents( WP_2FA_PATH . 'dist/images/wp-2fa-white-icon20x28.svg' ) ), // phpcs:ignore
81
);
\add_submenu_page(
self::TOP_MENU_SLUG,
\esc_html__( '2FA Policies', 'wp-2fa' ),
\esc_html__( '2FA Policies', 'wp-2fa' ),
'manage_options',
self::TOP_MENU_SLUG,
array( \WP2FA\Admin\SettingsPages\Settings_Page_Policies::class, 'render' ),
1
);
\add_submenu_page(
self::TOP_MENU_SLUG,
\esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
\esc_html__( 'Settings', 'wp-2fa' ),
'manage_options',
'wp-2fa-settings',
array( \WP2FA\Admin\SettingsPages\Settings_Page_Render::class, 'render' ),
2
);
/**
* Fires after the main menu settings are registered.
*
* @param string - The menu slug.
* @param bool - Is that multisite install or not.
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'after_admin_menu_created', self::TOP_MENU_SLUG, true );
}
/**
* Send account unlocked notification via email.
*
* @param int $user_id user ID.
*
* @return boolean
*/
public static function send_account_unlocked_email( $user_id ) {
// Bail if the user has not enabled this email.
if ( 'enable_account_unlocked_email' !== WP2FA::get_wp2fa_email_templates( 'send_account_unlocked_email' ) ) {
return false;
}
// Grab user data.
$user = get_userdata( $user_id );
// Grab user email.
$email = $user->user_email;
// Setup the email contents.
$subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_subject' ) ) );
$message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_body' ), $user_id ) );
return self::send_email( $email, $subject, $message );
}
/**
* Hide settings menu item
*/
public static function hide_settings() {
$user = wp_get_current_user();
// Check we have a user before doing anything else.
if ( is_a( $user, '\WP_User' ) ) {
if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
$main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
} else {
$main_user = get_current_user_id();
}
if ( ! empty( WP2FA::get_wp2fa_general_setting( 'limit_access' ) ) && $user->ID !== $main_user ) {
// Remove admin menu item.
remove_submenu_page( 'options-general.php', self::TOP_MENU_SLUG );
}
}
}
/**
* Add unlock user link to user actions.
*
* @param array $links Default row content.
*
* @return array
* @throws \Freemius_Exception - freemius exception.
*/
public static function add_plugin_action_links( $links ) {
// add link to the external free trial page in free version and also in premium version if license is not active.
if ( ! function_exists( 'wp2fa_freemius' ) || ! wp2fa_freemius()->has_active_valid_license() ) {
$trial_link = 'https://melapress.com/wordpress-2fa/pricing/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa';
$links = array_merge(
array(
'<a style="font-weight:bold" href="' . $trial_link . '" target="_blank">' . __( 'Upgrade to Premium', 'wp-2fa' ) . '</a>',
),
$links
);
}
// add link to the plugin settings page.
$url = Settings::get_settings_page_link();
$links = array_merge(
array(
'<a href="' . \esc_url( $url ) . '">' . \esc_html__( 'Configure 2FA Settings', 'wp-2fa' ) . '</a>',
),
$links
);
return $links;
}
/**
* Updates options for multisite
*
* @return void
*
* @since 2.0.0
*/
public static function update_wp2fa_network_options() {
Settings_Page_Policies::update_wp2fa_network_options();
Settings_Page_General::update_wp2fa_network_options();
\WP2FA\Admin\SettingsPages\Settings_Page_White_Label::update_wp2fa_network_options();
/**
* Gives the ability for extensions to set their settings in the plugin.
*
* @since 2.2.0
*/
do_action( WP_2FA_PREFIX . 'update_network_settings' );
}
/**
* Handle saving email options to the network main site options.
*/
public static function update_wp2fa_network_email_options() {
Settings_Page_Email::update_wp2fa_network_options();
}
/**
* These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed.
*/
public static function settings_saved_network_admin_notice() {
if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && 'true' === $_GET['wp_2fa_network_settings_updated'] ) {
?>
<div class="notice notice-success is-dismissible">
<p><?php \esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && 'false' === $_GET['wp_2fa_network_settings_updated'] ) { // phpcs:ignore
?>
<div class="notice notice-error is-dismissible">
<?php
if ( isset( $_GET['wp_2fa_network_settings_custom_error_message'] ) ) { // phpcs:ignore
$error = \sanitize_text_field( \wp_unslash( $_GET['wp_2fa_network_settings_custom_error_message'] ) );
?>
<p><?php echo \esc_attr( \esc_url_raw( \urldecode_deep( $error ) ) ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
<?php
} else {
?>
<p><?php \esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
<?php
}
?>
</div>
<?php
}
if ( isset( $_GET['wp_2fa_network_settings_error'] ) ) { // phpcs:ignore
?>
<div class="notice notice-error is-dismissible">
<?php
$error = \sanitize_text_field( \wp_unslash( $_GET['wp_2fa_network_settings_error'] ) );
if ( true === \strpos( $error, 'http' ) ) {
?>
<p><?php echo \esc_attr( \esc_url_raw( \urldecode_deep( $error ) ) ); ?></p>
<?php
} else {
?>
<p><?php echo \esc_attr( ( $error ) ); ?></p>
<?php } ?>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
}
/**
* These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed.
*
* @return void
*
* @since 2.0.0
*/
public static function settings_saved_admin_notice() {
if ( isset( $_GET['page'] ) && 0 === strpos( \sanitize_text_field( \wp_unslash( $_GET['page'] ) ), 'wp-2fa-' ) ) {
if ( isset( $_GET['settings-updated'] ) && 'true' === $_GET['settings-updated'] ) {
$wp_settings_errors = get_settings_errors();
if ( count( $wp_settings_errors ) ) {
foreach ( $wp_settings_errors as $error ) {
?>
<div class="notice notice-<?php echo \esc_attr( $error['type'] ); ?> is-dismissible">
<p><?php echo \esc_html( $error['message'] ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
} else {
?>
<div class="notice notice-success is-dismissible">
<p><?php \esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
}
if ( isset( $_GET['settings-updated'] ) && 'false' === $_GET['settings-updated'] ) {
?>
<div class="notice notice-error is-dismissible">
<p><?php \esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
if ( isset( $_GET['settings_error'] ) ) {
?>
<div class="notice notice-error is-dismissible">
<p><?php echo \esc_attr( \esc_url_raw( \urldecode_deep( \sanitize_text_field( \wp_unslash( $_GET['settings_error'] ) ) ) ) ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
</button>
</div>
<?php
}
}
}
/**
* Add our custom state to our created page.
*
* @param array $post_states - array with the post states.
* @param WP_Post $post - the WP post.
*
* @return array
*/
public static function add_display_post_states( $post_states, $post ) {
if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
if ( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) === $post->ID ) {
$post_states['wp_2fa_page_for_user'] = __( 'WP 2FA User Page', 'wp-2fa' );
}
}
return $post_states;
}
/**
* Handles sending of an email. It sets necessary header such as content type and custom from email address and name.
*
* @param string $recipient_email Email address to send message to.
* @param string $subject Email subject.
* @param string $message Message contents.
*
* @return bool Whether the email contents were sent successfully.
*/
public static function send_email( $recipient_email, $subject, $message ) {
// Specify our desired headers.
$headers = 'Content-type: text/html;charset=utf-8' . "\r\n";
if ( 'use-custom-email' === WP2FA::get_wp2fa_email_templates( 'email_from_setting' ) ) {
$headers .= 'From: ' . WP2FA::get_wp2fa_email_templates( 'custom_from_display_name' ) . ' <' . WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ) . '>' . "\r\n";
} else {
$headers .= 'From: wp2fa <' . self::get_default_email_address() . '>' . "\r\n";
// $headers .= 'From: ' . get_bloginfo( 'name' ) . ' <' . get_bloginfo( 'admin_email' ) . '>' . "\r\n";
}
// Fire our email.
return wp_mail( $recipient_email, stripslashes_deep( html_entity_decode( $subject, ENT_QUOTES, 'UTF-8' ) ), $message, $headers );
}
/**
* Builds and returns the default email address used for the "from" email address when email is send
*
* @return string
*
* @since 2.6.4
*/
public static function get_default_email_address(): string {
$sitename = wp_parse_url( network_home_url(), PHP_URL_HOST );
$from_email = 'wp2fa@';
if ( null !== $sitename ) {
if ( str_starts_with( $sitename, 'www.' ) ) {
$sitename = substr( $sitename, 4 );
}
$from_email .= $sitename;
}
return $from_email;
}
/**
* Turns user roles data in any form and shape to an array of strings.
*
* @param mixed $value User role names (slugs) as raw value.
*
* @return string[] List of user role names (slugs).
*/
public static function extract_roles_from_input( $value ) {
if ( is_array( $value ) ) {
return $value;
}
if ( is_string( $value ) && ! empty( $value ) ) {
return explode( ',', $value );
}
return array();
}
/**
* Determine if any BG processes are currently running.
*
* @return int|false Number of jobs.
*/
public static function get_current_number_of_active_bg_processes() {
global $wpdb;
$bg_jobs = $wpdb->get_results( // phpcs:ignore
"SELECT option_value FROM $wpdb->options
WHERE option_name LIKE '%_2fa_bg_%'"
);
return count( $bg_jobs );
}
/**
* Checks the email against the current domain and shows an error message if they do not match.
*
* @return void
*
* @since 2.6.0
*/
public static function check_email() {
$is_dismissed = (bool) Settings_Utils::get_option( 'dismiss_notice_mail_domain', false );
if ( ! $is_dismissed ) {
$admin_email = null;
if ( 'use-custom-email' === WP2FA::get_wp2fa_email_templates( 'email_from_setting' ) ) {
$admin_email = WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' );
}
if ( '' === trim( (string) $admin_email ) ) {
$email_settings_url = \esc_url(
add_query_arg(
array(
'page' => 'wp-2fa-settings',
'tab' => 'email-settings',
),
network_admin_url( 'admin.php' )
)
);
?>
<div class="notice notice-error" style="padding-top: 10px; padding-bottom: 10px;">
<p class="description" ><?php \esc_html_e( 'By default, the plugin uses ', 'wp-2fa' ); ?> <b><?php echo \sanitize_email( self::get_default_email_address() ); ?></b> <?php \esc_html_e( 'as the "from address" when sending emails with the 2FA code for users to log in. Do you want to keep using this or change it?', 'wp-2fa' ); ?></p>
<p>
<a class="button button-primary" href="<?php echo \esc_url( $email_settings_url ); ?>"><?php \esc_html_e( 'Change it', 'wp-2fa' ); ?></a>
<a class="button button-secondary 2fa-email-notice" style="margin-left:20px" href="#">
<?php \esc_html_e( 'Keep using it', 'wp-2fa' ); ?>
</a>
</p>
<?php wp_nonce_field( 'wp2fa_dismiss_notice_mail_domain', 'wp2fa_dismiss_notice_mail_domain', false ); ?>
</div>
<?php
} else {
Settings_Utils::update_option( 'dismiss_notice_mail_domain', true );
}
}
}
/**
* Sets the email domain do not match setting as dismissed.
*
* @return void
*
* @since 2.6.0
*/
public static function dismiss_notice_mail_domain() {
// Verify nonce.
if ( isset( $_POST['nonce'] ) && \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['nonce'] ) ), 'wp2fa_dismiss_notice_mail_domain' ) ) {
Settings_Utils::update_option( 'dismiss_notice_mail_domain', true );
die();
}
die( 'Nonce verification failed!' );
}
}
}
includes/classes/Admin/Methods/class-totp.php 0000644 00000036351 15075513060 0015256 0 ustar 00 <?php
/**
* Responsible for WP2FA user's TOTP manipulation.
*
* @package wp2fa
* @subpackage methods
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*
* @since 2.6.0
*/
declare(strict_types=1);
namespace WP2FA\Methods;
use WP2FA\WP2FA;
use WP2FA\Admin\User_Profile;
use WP2FA\Authenticator\Open_SSL;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Authenticator\Authentication;
use WP2FA\Methods\Wizards\TOTP_Wizard_Steps;
/**
* Class for handling totp codes.
*
* @since 2.6.0
*
* @package WP2FA
*/
if ( ! class_exists( '\WP2FA\Methods\TOTP' ) ) {
/**
* TOTP code class, for handling totp (app) code generation and such.
*
* @since 2.6.0
*/
class TOTP {
/**
* The name of the method.
*
* @var string
*
* @since 2.6.0
*/
public const METHOD_NAME = 'totp';
/**
* Secret TOTP key meta name.
*
* @var string
*
* @since 2.6.0
*/
public const TOTP_META_KEY = WP_2FA_PREFIX . 'totp_key';
/**
* The name of the method stored in the policy
*
* @var string
*
* @since 2.6.0
*/
public const POLICY_SETTINGS_NAME = 'enable_totp';
/**
* Is the totp method enabled
*
* @since 1.7
*
* @var bool
*/
private static $totp_enabled = null;
/**
* Totp key assigned to user
*
* @var string
*/
private static $totp_key = '';
/**
* Inits the class and sets the filters.
*
* @return void
*
* @since 2.6.0
*/
public static function init() {
\add_filter( WP_2FA_PREFIX . 'providers_translated_names', array( __CLASS__, 'totp_provider_name_translated' ) );
\add_filter( WP_2FA_PREFIX . 'providers', array( __CLASS__, 'totp_provider' ) );
\add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
\add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'settings_loop' ), 10, 1 );
\add_filter( WP_2FA_PREFIX . 'no_method_enabled', array( __CLASS__, 'return_default_selection' ), 10, 1 );
// add the TOTP methods to the list of available methods if enabled.
\add_filter(
WP_2FA_PREFIX . 'available_2fa_methods',
function ( $available_methods ) {
if ( ! empty( Settings::get_role_or_default_setting( self::POLICY_SETTINGS_NAME, 'current' ) ) ) {
array_push( $available_methods, self::METHOD_NAME );
}
return $available_methods;
}
);
TOTP_Wizard_Steps::init();
}
/**
* Adds TOTP translated name
*
* @param array $providers - Array with all currently supported providers and their translated names.
*
* @return array
*
* @since 2.6.0
*/
public static function totp_provider_name_translated( array $providers ) {
$providers[ self::METHOD_NAME ] = esc_html__( 'TOTP (one-time code via app)', 'wp-2fa' );
return $providers;
}
/**
* Extracts the selected value from the global settings (if set), and adds it to the output array
*
* @param array $output - The array with output values.
*
* @return array
*
* @since 2.6.0
*/
public static function return_default_selection( array $output ) {
// No method is enabled, fall back to previous selected one - we don't want to break the logic.
$totp_enabled = WP2FA::get_wp2fa_setting( self::POLICY_SETTINGS_NAME );
if ( $totp_enabled ) {
$output[ self::POLICY_SETTINGS_NAME ] = $totp_enabled;
}
return $output;
}
/**
* Sets the TOTP as a method for the given user
*
* @param \WP_User $user - The user for which the method has to be set, if null, it uses the current user.
* @param string $totp_key - The totp key for the user to be set.
*
* @return void
*
* @throws \LogicException - If the method is called without $totp_key.
*
* @since 2.6.0
*/
public static function set_user_method( $user = null, string $totp_key = '' ) {
if ( null === $user ) {
$user = wp_get_current_user();
}
if ( '' === \trim( $totp_key ) ) {
throw new \LogicException( 'TOTP key must not be empty' );
}
User_Helper::set_enabled_method_for_user( self::METHOD_NAME, $user );
self::set_user_totp_key( $totp_key, $user );
User_Profile::delete_expire_and_enforced_keys( $user->ID );
User_Helper::set_user_status( $user );
}
/**
* Adds TOTP as a provider
*
* @param array $providers - Array with all currently supported providers.
*
* @return array
*
* @since 2.6.0
*/
public static function totp_provider( array $providers ) {
array_push( $providers, self::METHOD_NAME );
return $providers;
}
/**
* Retrieves the QR code
*
* @since 2.6.0
*
* @return string
*/
public static function get_qr_code(): string {
// Setup site information, used when generating our QR code.
$site_name = site_url();
$site_name = trim( str_replace( array( 'http://', 'https://' ), '', (string) $site_name ), '/' );
/**
* Changing the title of the login screen for the TOTP method.
*
* @param string $title - The default title.
* @param \WP_User $user - The WP user.
*
* @since 2.0.0
*/
$totp_title = apply_filters(
WP_2FA_PREFIX . 'totp_title',
$site_name . ':' . User_Helper::get_user_object()->user_login,
User_Helper::get_user_object()
);
return Authentication::get_google_qr_code( $totp_title, self::get_totp_key(), $site_name );
}
/**
* Validates authentication.
*
* @param \WP_User $user - The WP user, if presented.
*
* @return bool Whether the user gave a valid code
*
* @since 2.6.0
*/
public static function validate_totp_authentication( \WP_User $user = null ) {
if ( ! empty( $_REQUEST['authcode'] ) ) { //phpcs:ignore
$valid = Authentication::is_valid_authcode(
self::get_totp_key( $user ),
\sanitize_text_field( \wp_unslash( $_REQUEST['authcode'] ) )
);
if ( $valid ) {
Authentication::clear_login_attempts( $user );
} else {
Authentication::increase_login_attempts( $user );
}
return $valid;
}
return false;
}
/**
* Add extension settings to the loop array
*
* @param array $loop_settings - Currently available settings array.
*
* @return array
*
* @since 2.6.0
*/
public static function settings_loop( array $loop_settings ) {
array_push( $loop_settings, self::POLICY_SETTINGS_NAME );
return $loop_settings;
}
/**
* Returns the status of the totp method (enabled | disabled)
*
* @since 2.6.0
*
* @return boolean
*/
public static function is_enabled(): bool {
if ( null === self::$totp_enabled ) {
self::$totp_enabled = empty( Settings::get_role_or_default_setting( self::POLICY_SETTINGS_NAME, 'current' ) ) ? false : true;
}
return self::$totp_enabled;
}
/**
* Regenerates the TOTP key for the user
*
* @return void - JSON - object with "key" - stores the new key and "qr" - stores the new QR code.
*
* @since 2.5.0
*/
public static function regenerate_authentication_key() {
// Grab current user.
$user = wp_get_current_user();
$key = Authentication::generate_key();
$site_name = site_url();
$site_name = trim( str_replace( array( 'http://', 'https://' ), '', (string) $site_name ), '/' );
/**
* Changing the title of the login screen for the TOTP method.
*
* @param string $title - The default title.
* @param \WP_User $user - The WP user.
*
* @since 2.0.0
*/
$totp_title = apply_filters( WP_2FA_PREFIX . 'totp_title', $site_name . ':' . $user->user_login, $user );
$new_qr = Authentication::get_google_qr_code( $totp_title, $key, $site_name );
wp_send_json_success(
array(
'key' => Authentication::decrypt_key_if_needed( $key ),
'qr' => $new_qr,
)
);
}
/**
* Adds the extension default settings to the main plugin settings
*
* @param array $default_settings - array with plugin default settings.
*
* @return array
*
* @since 2.6.0
*/
public static function add_default_settings( array $default_settings ) {
$default_settings[ self::POLICY_SETTINGS_NAME ] = self::POLICY_SETTINGS_NAME;
$default_settings['method_help_totp_intro'] = '<h3>' . __( 'Setting up TOTP (one-time code via app)', 'wp-2fa' ) . '</h3>';
$default_settings['method_help_totp_step_1'] = __( 'Download and start the application of your choice', 'wp-2fa' );
$default_settings['method_help_totp_step_2'] = __( 'From within the application scan the QR code provided on the left. Otherwise, enter the following code manually in the application:', 'wp-2fa' );
$default_settings['method_help_totp_step_3'] = __( 'Click the "I\'m ready" button below when you complete the application setup process to proceed with the wizard.', 'wp-2fa' );
$default_settings['method_verification_totp_pre'] = '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code from your chosen authentication app to finalize the setup.', 'wp-2fa' ) . '</p>';
$default_settings['totp_reconfigure_intro'] = '<h3>' . __( '{reconfigure_or_configure_capitalized} the 2FA App', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the current 2FA method. Note that once reset you will have to re-scan the QR code on all devices you want this to work on because the previous codes will stop working.', 'wp-2fa' ) . '</p>';
$default_settings['totp-option-label'] = __( 'One-time code via 2FA app', 'wp-2fa' );
$default_settings['totp-option-label-hint'] = sprintf(
/* translators: link to the knowledge base website */
\esc_html__( 'Refer to the %s for more information on how to setup these apps and which apps are supported.', 'wp-2fa' ),
'<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>'
);
return $default_settings;
}
/**
* User totp key getter
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return string
*
* @since 2.6.0
*/
public static function get_totp_key( $user = null ): string {
if ( '' === trim( (string) self::$totp_key ) ) {
self::$totp_key = self::get_user_totp_key_auth( User_Helper::get_user( $user )->ID );
if ( empty( self::$totp_key ) ) {
self::$totp_key = Authentication::generate_key();
self::set_user_totp_key( self::$totp_key, $user );
} elseif ( Open_SSL::is_ssl_available() && false === \strpos( self::$totp_key, Open_SSL::SECRET_KEY_PREFIX ) ) {
self::$totp_key = Open_SSL::SECRET_KEY_PREFIX . Open_SSL::encrypt( self::$totp_key );
self::set_user_totp_key( self::$totp_key, $user );
}
}
return self::$totp_key;
}
/**
* Returns the encoded TOTP when we need to show the actual code to the user
* If for some reason the code is invalid it recreates it
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return string
*
* @since 2.6.0
*/
public static function get_totp_decrypted( $user = null ): string {
$key = self::get_totp_key( $user );
if ( Open_SSL::is_ssl_available() && false !== \strpos( $key, 'ssl_' ) ) {
/**
* Old key detected - convert.
*/
$key = Open_SSL::decrypt_legacy( substr( $key, 4 ) );
self::remove_user_totp_key( $user );
self::$totp_key = '';
$key = self::get_totp_key( $user );
}
if ( Open_SSL::is_ssl_available() && false !== \strpos( $key, 'wps_' ) ) {
/**
* Old key detected - convert.
*/
$key = Open_SSL::decrypt_wps( substr( $key, 4 ) );
self::remove_user_totp_key( $user );
$secret = Open_SSL::encrypt( $key );
if ( Open_SSL::is_ssl_available() ) {
$secret = Open_SSL::SECRET_KEY_PREFIX . $secret;
}
self::set_user_totp_key( $secret, $user );
self::$totp_key = $secret;
}
if ( Open_SSL::is_ssl_available() && false !== \strpos( $key, Open_SSL::SECRET_KEY_PREFIX ) ) {
$key = Open_SSL::decrypt( substr( $key, 4 ) );
/**
* If for some reason the key is not valid, that means that we have to clear the stored TOTP for the user, and create new on
* That could happen if the global stored secret (plugin level) is deleted.
*
* Lets check and if that is the case - create new one
*/
if ( ! Authentication::validate_base32_string( $key ) ) {
self::$totp_key = '';
self::remove_user_totp_key( $user );
$key = self::get_totp_key( $user );
$key = Open_SSL::decrypt( substr( $key, 4 ) );
}
}
return $key;
}
/**
* Deletes the TOTP secret key for a user.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return void
*/
public static function remove_user_totp_key( $user = null ) {
User_Helper::remove_meta( self::TOTP_META_KEY, $user );
self::$totp_key = '';
}
/**
* Returns the TOTP secret key for a user.
*
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return string
*/
public static function get_user_totp_key( $user = null ) {
return User_Helper::get_meta( self::TOTP_META_KEY, $user );
}
/**
* Updates the TOTP secret key for a user.
*
* @param string $value - The value of the TOTP key.
* @param int|\WP_User|null $user - The WP user that must be used.
*
* @return void
*
* @since 2.2.0
*/
public static function set_user_totp_key( string $value, $user = null ) {
User_Helper::set_meta( self::TOTP_META_KEY, $value, $user );
}
/**
* Get the TOTP secret key for a user.
*
* @param int $user_id User ID.
*
* @return string
*
* @since 2.6.0
*/
public static function get_user_totp_key_auth( $user_id ) {
$key = (string) self::get_user_totp_key( $user_id );
$test = $key;
if ( Open_SSL::is_ssl_available() && false !== \strpos( $key, 'ssl_' ) ) {
/**
* Old key detected - convert.
*/
$key = Open_SSL::decrypt_legacy( substr( $key, 4 ) );
self::remove_user_totp_key();
$secret = Open_SSL::encrypt( $key );
if ( Open_SSL::is_ssl_available() ) {
$secret = Open_SSL::SECRET_KEY_PREFIX . $secret;
}
self::set_user_totp_key( $key, $user_id );
$test = $key = (string) self::get_user_totp_key( $user_id ); // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
}
// We've tried tried to use WP core functionality, but that doesn't work - lets update.
if ( Open_SSL::is_ssl_available() && false !== \strpos( $key, 'wps_' ) ) {
/**
* Old key detected - convert.
*/
$key = Open_SSL::decrypt_wps( substr( $key, 4 ) );
self::remove_user_totp_key();
$secret = Open_SSL::encrypt( $key );
if ( Open_SSL::is_ssl_available() ) {
$secret = Open_SSL::SECRET_KEY_PREFIX . $secret;
}
self::set_user_totp_key( $key, $user_id );
$test = $key = (string) self::get_user_totp_key( $user_id ); // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
}
Authentication::decrypt_key_if_needed( $test );
if ( ! Authentication::is_valid_key( $test ) ) {
$key = Authentication::generate_key();
self::set_user_totp_key( $key, $user_id );
Authentication::clear_decrypted_key();
}
return $key;
}
}
}
includes/classes/Admin/Methods/Traits/index.php 0000644 00000000046 15075513060 0015532 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/Methods/Traits/class-login-attempts.php 0000644 00000006052 15075513060 0020500 0 ustar 00 <?php
/**
* Responsible for the plugin login attempts
*
* @package wp2fa
* @subpackage traits
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Methods\Traits;
use WP2FA\Admin\Helpers\User_Helper;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* Responsible for the login attempts
*
* @since 2.4.1
*/
trait Login_Attempts {
/**
* Holds the number of allowed attempts to login
*
* @var integer
*
* @since 2.4.1
*/
private static $number_of_allowed_attempts = 3;
/**
* Increasing login attempts for User
*
* @since 2.4.1
*
* @param \WP_User $user - the WP User.
*
* @return void
*/
public static function increase_login_attempts( \WP_User $user ) {
$attempts = self::get_login_attempts( $user );
if ( '' === $attempts ) {
$attempts = 0;
}
User_Helper::set_meta( self::$logging_attempts_meta_key, ++$attempts, $user );
}
/**
* Returns the number of unsuccessful attempts for the User
*
* @since 2.4.1
*
* @param \WP_User $user - the WP User.
*
* @return integer
*/
public static function get_login_attempts( \WP_User $user ): int {
return (int) User_Helper::get_meta( self::$logging_attempts_meta_key, $user );
}
/**
* Clearing login attempts for User
*
* @since 2.4.1
*
* @param \WP_User $user - the WP User.
*
* @return void
*/
public static function clear_login_attempts( \WP_User $user ) {
User_Helper::remove_meta( self::$logging_attempts_meta_key, $user );
}
/**
* Returns the number of allowed login attempts
*
* @return integer
*
* @since 2.4.1
*/
public static function get_allowed_login_attempts(): int {
return self::$number_of_allowed_attempts;
}
/**
* Sets the number of allowed attempts
*
* @param integer $number - The number of the allowed attempts.
*
* @return integer
*
* @since 2.4.1
*/
public static function set_number_of_login_attempts( int $number ): int {
self::$number_of_allowed_attempts = $number;
return self::$number_of_allowed_attempts;
}
/**
* Returns the name of the meta key holding the login attempts for the user
*
* @return string
*
* @since 2.4.1
*/
public static function get_meta_key(): string {
return self::$logging_attempts_meta_key;
}
/**
* Sets the login attempts meta key
*
* @param string $logging_attempts_meta_key - The name of the meta.
*
* @return string
*
* @since 2.4.1
*/
public static function set_meta_key( string $logging_attempts_meta_key ): string {
self::$logging_attempts_meta_key = $logging_attempts_meta_key;
return self::$logging_attempts_meta_key;
}
/**
* Checks the number of login attempts
*
* @param \WP_User $user - The user we have to check for.
*
* @return boolean
*
* @since 2.4.1
*/
public static function check_number_of_attempts( \WP_User $user ): bool {
if ( self::get_allowed_login_attempts() < self::get_login_attempts( $user ) ) {
return false;
}
return true;
}
}
includes/classes/Admin/Methods/Traits/class-methods-wizards-trait.php 0000644 00000006055 15075513060 0022001 0 ustar 00 <?php
/**
* Responsible for the plugin wizard ordering
*
* @package wp2fa
* @subpackage traits
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin\Methods\Traits;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Helpers\Methods_Helper;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
/**
* Responsible for the login attempts
*
* @since 2.6.0
*/
trait Methods_Wizards_Trait {
/**
* Returns the order in the wizard
*
* @param string $role - The name of the role - could be empty.
* @param array $methods - The array with currently collected methods.
*
* @return integer
*
* @since 2.6.0
*/
public static function get_order( string $role = null, array $methods = array() ): int {
if ( null !== $role && ! empty( $role ) && class_exists( '\WP2FA\Extensions\RoleSettings\Role_Settings_Controller' ) ) {
$methods_order = Role_Settings_Controller::get_setting( $role, Methods_Helper::POLICY_SETTINGS_NAME );
if ( \is_array( $methods_order ) ) {
$methods_order = \array_flip( $methods_order );
if ( isset( $methods_order[ self::get_main_class()::METHOD_NAME ] ) ) {
static::$order = (int) $methods_order[ self::get_main_class()::METHOD_NAME ];
}
}
} else {
$use_role_setting = null;
if ( null === $role || '' === trim( (string) $role ) ) {
$use_role_setting = \WP_2FA_PREFIX . 'no-user';
}
$methods_order = Settings::get_role_or_default_setting( Methods_Helper::POLICY_SETTINGS_NAME, $use_role_setting, $role, true );
if ( \is_array( $methods_order ) ) {
$methods_order = \array_flip( $methods_order );
if ( isset( $methods_order[ self::get_main_class()::METHOD_NAME ] ) ) {
static::$order = (int) $methods_order[ self::get_main_class()::METHOD_NAME ];
}
}
}
if ( isset( $methods[ static::$order ] ) ) {
// Obviously we have a problem here - such order already exists in the methods array, so grab the biggest order and increase it.
// TODO: maybe we need to update the settings for that method as well ?
static::$order = max( array_keys( $methods ) );
++static::$order;
}
return static::$order;
}
/**
* Returns the main class of the given wizard steps class.
*
* @return string
*
* @since 2.6.0
*/
public static function get_main_class(): string {
return static::$main_class;
}
/**
* Creates hidden field for the method order
*
* @param string $role - The name of the role (if present).
*
* @return string
*
* @since 2.6.0
*/
public static function hidden_order_setting( string $role = null ): string {
$name_prefix = WP_2FA_POLICY_SETTINGS_NAME;
if ( null !== $role && '' !== trim( (string) $role ) ) {
$name_prefix .= "[{$role}]";
}
$hidden_field = '<input type="hidden" name="' . \esc_attr( $name_prefix ) . '[methods_order][]" value="' . \esc_attr( self::get_main_class()::METHOD_NAME ) . '">';
return $hidden_field;
}
}
includes/classes/Admin/Methods/class-email-wizard-steps.php 0000644 00000034572 15075513060 0020014 0 ustar 00 <?php
/**
* Responsible for WP2FA user's Email manipulation.
*
* @package wp2fa
* @subpackage methods-wizard
* @since 2.6.0
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Methods\Wizards;
use WP2FA\WP2FA;
use WP2FA\Methods\Email;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Views\Wizard_Steps;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Methods\Traits\Methods_Wizards_Trait;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
/**
* Class for handling email codes.
*
* @since 2.6.0
*
* @package WP2FA
*/
if ( ! class_exists( '\WP2FA\Methods\Wizards\Email_Wizard_Steps' ) ) {
/**
* Email code class, for handling email code generation and such.
*
* @since 2.6.0
*/
class Email_Wizard_Steps extends Wizard_Steps {
use Methods_Wizards_Trait;
/**
* Keeps the main class method name, so we can call it when needed.
*
* @var string
*
* @since 2.6.0
*/
private static $main_class = Email::class;
/**
* The default value of the method order in the wizards.
*
* @var integer
*
* @since 2.6.0
*/
private static $order = 2;
/**
* Inits the class hooks
*
* @return void
*
* @since 2.4.0
*/
public static function init() {
\add_filter( WP_2FA_PREFIX . 'methods_modal_options', array( __CLASS__, 'email_option' ), 10, 2 );
\add_action( WP_2FA_PREFIX . 'modal_methods', array( __CLASS__, 'email_modal_configure' ) );
\add_filter( WP_2FA_PREFIX . 'methods_re_configure', array( __CLASS__, 'email_re_configure' ), 10, 2 );
\add_filter( WP_2FA_PREFIX . 'methods_settings', array( __CLASS__, 'email_wizard_settings' ), 10, 4 );
}
/**
* Shows the option for email method reconfiguring (if applicable)
*
* @param array $methods - Array of methods collected.
* @param string $role - The name of the role to show option to.
*
* @since 2.6.0 - Parameter $methods is added, parameter $role (name) is added and array is now returned
*
* @return array
*/
public static function email_re_configure( array $methods, string $role ): array {
if ( ! Email::is_enabled() ) {
return $methods;
}
\ob_start();
?>
<div class="option-pill">
<?php echo \wp_kses_post( WP2FA::contextual_reconfigure_text( WP2FA::get_wp2fa_white_label_setting( 'hotp_reconfigure_intro', true ), User_Helper::get_user_object()->ID, 'hotp' ) ); ?>
<div class="wp2fa-setup-actions">
<a class="button button-primary wp-2fa-button-primary" data-name="next_step_setting_modal_wizard" value="<?php \esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-send-setup-email' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> data-next-step="2fa-wizard-email"><?php \esc_html_e( 'Change email address', 'wp-2fa' ); ?></a>
</div>
</div>
<?php
$output = ob_get_contents();
ob_end_clean();
$methods[ self::get_order( $role, $methods ) ] = array(
'name' => self::$main_class::METHOD_NAME,
'output' => $output,
);
return $methods;
}
/**
* Shows the initial email setup options based on enabled methods
*
* @param array $methods - Array of methods collected.
* @param string $role - The name of the role to show option to.
*
* @since 2.6.0 - Parameter $methods is added, parameter $role (name) is added and array is now returned
*
* @return array
*/
public static function email_option( array $methods, string $role ): array {
if ( Email::is_enabled() ) {
\ob_start();
?>
<div class="option-pill">
<label for="geek">
<input id="geek" name="wp_2fa_enabled_methods" type="radio" value="email">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'email-option-label', true ) ); ?>
</label>
</div>
<?php
$output = ob_get_contents();
ob_end_clean();
$methods[ self::get_order( $role, $methods ) ] = $output;
}
return $methods;
}
/**
* Settings page and first time wizard settings render
*
* @param array $methods - Array with all the methods in which we have to add this one.
* @param boolean $setup_wizard - Is that the first time setup wizard.
* @param string $data_role - Additional HTML data attribute.
* @param mixed $role - Name of the role.
*
* @return array - The array with the methods with all the methods wizard steps.
*
* @since 2.6.0
*/
public static function email_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) {
$name_prefix = \WP_2FA_POLICY_SETTINGS_NAME;
$role_id = '';
if ( null !== $role && '' !== trim( (string) $role ) ) {
$name_prefix .= "[{$role}]";
$data_role = 'data-role="' . $role . '"';
$role_id = '-' . $role;
}
\ob_start();
?>
<div id="<?php echo \esc_attr( Email::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper">
<?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<label for="hotp<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;">
<input type="checkbox" id="hotp<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_email]" value="enable_email"
<?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if ( null !== $role && ! empty( $role ) ) { ?>
<?php \checked( Email::POLICY_SETTINGS_NAME, Role_Settings_Controller::get_setting( $role, Email::POLICY_SETTINGS_NAME ), true ); ?>
<?php
} else {
$use_role_setting = null;
if ( null === $role || '' === trim( (string) $role ) ) {
$use_role_setting = \WP_2FA_PREFIX . 'no-user';
}
$enabled_settings = Settings::get_role_or_default_setting( Email::POLICY_SETTINGS_NAME, $use_role_setting, $role, true );
?>
<?php \checked( $enabled_settings, Email::POLICY_SETTINGS_NAME ); ?>
<?php } ?>
>
<?php
\esc_html_e( 'One-time code via email (HOTP)', 'wp-2fa' );
\esc_html_e( ' - ensure email deliverability with the free plugin ', 'wp-2fa' );
echo '<a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank" rel="nofollow">WP Mail SMTP</a>.';
?>
</label>
<?php
if ( $setup_wizard ) {
echo '<p class="description">' . \esc_html__( 'When using this method, users will receive the one-time login code over email. Therefore, email deliverability is very important. Users using this method should whitelist the address from which the codes are sent. By default, this is the email address configured in your WordPress. You can run an email test from the plugin\'s settings to confirm email deliverability. If you have had email deliverability / reliability issues, we highly recommend you to install the free plugin ', 'wp-2fa' ) . '<a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank" rel="nofollow">WP Mail SMTP</a><br><br>' . \esc_html__( 'Allowing users to set up a secondary 2FA method is highly recommended. You can do this in the next step of the wizard. This will allow users to log in using an alternative method should they, for example lose access to their phone.', 'wp-2fa' ) . '</p>';
}
?>
<?php
if ( null !== $role ) {
$enabled_settings = Role_Settings_Controller::get_setting( $role, 'enable_email' );
} else {
$enabled_settings = Settings::get_role_or_default_setting( 'enable_email', ( ( null !== $role && '' !== $role ) ? '' : false ), $role, true, true );
}
?>
<?php
?>
<?php if ( ! $setup_wizard ) { ?>
<div class="use-different-hotp-mail<?php echo \esc_attr( ( false === $enabled_settings ? ' disabled' : '' ) ); ?>">
<p class="description">
<?php \esc_html_e( 'Allow user to specify the email address of choice', 'wp-2fa' ); ?>
</p>
<fieldset class="email-hotp-options">
<?php
$options = array(
'yes' => array(
'label' => \esc_html__( 'Yes', 'wp-2fa' ),
'value' => 'specify-email_hotp',
),
'no' => array(
'label' => \esc_html__( 'No', 'wp-2fa' ),
'value' => '',
),
);
foreach ( $options as $option_key => $option_settings ) {
?>
<label for="specify-email_hotp-<?php echo \esc_attr( $option_key ); ?>">
<input type="radio"
name="<?php echo \esc_attr( $name_prefix ); ?>[specify-email_hotp]"
<?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
id="specify-email_hotp-<?php echo \esc_attr( $option_key ); ?>"
value="<?php echo \esc_attr( $option_settings['value'] ); ?>" class="js-nested"
<?php if ( null !== $role ) { ?>
<?php \checked( Role_Settings_Controller::get_setting( $role, 'specify-email_hotp' ), $option_settings['value'] ); ?>
<?php
} else {
$use_role_setting = null;
if ( null === $role || '' === trim( (string) $role ) ) {
$use_role_setting = \WP_2FA_PREFIX . 'no-user';
}
\checked( Settings::get_role_or_default_setting( 'specify-email_hotp', $use_role_setting, $role, true, false ), $option_settings['value'] );
?>
<?php } ?>
>
<span><?php echo $option_settings['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
</label>
<?php
}
?>
</fieldset>
</div>
<?php } ?>
</div>
<?php
$output = ob_get_contents();
ob_end_clean();
$methods[ self::get_order( $role, $methods ) ] = $output;
return $methods;
}
/**
* Reconfigures email form
*
* @since 2.6.0
*
* @return void
*/
public static function email_modal_configure() {
if ( ! Email::is_enabled() ) {
return;
}
?>
<div class="wizard-step" id="2fa-wizard-email">
<fieldset>
<div class="step-setting-wrapper active">
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_intro', true ) ); ?>
</div>
<fieldset class="radio-cells">
<div class="option-pill">
<label for="use_wp_email">
<input type="radio" name="wp_2fa_email_address" id="use_wp_email" value="<?php echo \esc_attr( User_Helper::get_user_object()->user_email ); ?>" checked>
<span><?php \esc_html_e( 'Use my user email (', 'wp-2fa' ); ?><small><?php echo \esc_attr( User_Helper::get_user_object()->user_email ); ?></small><?php \esc_html_e( ')', 'wp-2fa' ); ?></span>
</label>
</div>
<?php
if ( Settings::get_role_or_default_setting( 'specify-email_hotp', User_Helper::get_user_object() ) ) {
?>
<div class="option-pill">
<label for="use_custom_email">
<input type="radio" name="wp_2fa_email_address" id="use_custom_email" value="use_custom_email">
<span><?php \esc_html_e( 'Use a different email address:', 'wp-2fa' ); ?></span>
<?php \esc_html_e( 'Email address', 'wp-2fa' ); ?>
<input type="email" name="custom-email-address" id="custom-email-address" class="input" value=""/>
</label>
</div>
<?php
}
?>
</fieldset>
<p class="description"><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_help', true ) ); ?></p><br>
<?php
$from_email = \get_option( 'admin_email' );
$custom_mail = WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' );
if ( isset( $custom_mail ) && ! empty( (string) $custom_mail ) ) {
$from_email = $custom_mail;
}
echo \wp_kses_post( str_replace( '{from_email}', $from_email, WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_help_email', true ) ) );
?>
<div class="wp2fa-setup-actions">
<button class="button button-primary wp-2fa-button-primary" name="next_step_setting_email_verify" value="<?php \esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" data-trigger-setup-email data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-send-setup-email' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> type="button"><?php \esc_html_e( 'I\'m Ready', 'wp-2fa' ); ?></button>
<a class="button button-primary wp-2fa-button-primary modal_cancel"><?php \esc_attr_e( 'Cancel', 'wp-2fa' ); ?></a>
</div>
</div>
<div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Verify configuration', 'wp-2fa' ); ?>" id="2fa-wizard-email">
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_verification_hotp_pre', true ) ); ?>
</div>
<fieldset>
<label for="2fa-email-authcode">
<?php \esc_html_e( 'Authentication Code', 'wp-2fa' ); ?>
<input type="tel" name="wp-2fa-email-authcode" id="wp-2fa-email-authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off"/>
<script>
const email_authcode = document.getElementById('wp-2fa-email-authcode');
email_authcode.addEventListener('input', function() {
this.value = this.value.trim();
});
</script>
</label>
<div class="verification-response"></div>
</fieldset>
<br />
<a href="#" class="button wp-2fa-button-primary" data-validate-authcode-ajax <?php echo WP_Helper::create_data_nonce( 'wp-2fa-validate-authcode' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php \esc_html_e( 'Validate & Save', 'wp-2fa' ); ?></a>
<a href="#" class="button wp-2fa-button-primary resend-email-code" data-trigger-setup-email data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-send-setup-email' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<span class="resend-inner"><?php \esc_html_e( 'Send me another code', 'wp-2fa' ); ?></span>
</a>
<button class="wp-2fa-button-secondary button" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
</div>
</fieldset>
</div>
<?php
}
}
}
includes/classes/Admin/Methods/class-email.php 0000644 00000014063 15075513060 0015353 0 ustar 00 <?php
/**
* Responsible for WP2FA user's email method manipulation.
*
* @package wp2fa
* @subpackage methods
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*
* @since 2.6.0
*/
declare(strict_types=1);
namespace WP2FA\Methods;
use WP2FA\WP2FA;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Methods\Wizards\Email_Wizard_Steps;
/**
* Class for handling email codes.
*
* @since 2.6.0
*
* @package WP2FA
*/
if ( ! class_exists( '\WP2FA\Methods\Email' ) ) {
/**
* Email code class, for handling email method code generation and such.
*
* @since 2.6.0
*/
class Email {
/**
* The name of the method.
*
* @var string
*
* @since 2.6.0
*/
public const METHOD_NAME = 'email';
/**
* The name of the method stored in the policy
*
* @var string
*
* @since 2.6.0
*/
public const POLICY_SETTINGS_NAME = 'enable_email';
/**
* Is the mail enabled
*
* @since 2.6.0
*
* @var bool
*/
private static $email_enabled = null;
/**
* Inits the class and sets the filters.
*
* @return void
*
* @since 2.6.0
*/
public static function init() {
\add_filter( WP_2FA_PREFIX . 'providers_translated_names', array( __CLASS__, 'email_provider_name_translated' ) );
\add_filter( WP_2FA_PREFIX . 'providers', array( __CLASS__, 'email_provider' ) );
\add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
\add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'settings_loop' ), 10, 1 );
\add_filter( WP_2FA_PREFIX . 'no_method_enabled', array( __CLASS__, 'return_default_selection' ), 10, 1 );
// add the TOTP methods to the list of available methods if enabled.
\add_filter(
WP_2FA_PREFIX . 'available_2fa_methods',
function ( $available_methods ) {
if ( ! empty( Settings::get_role_or_default_setting( self::POLICY_SETTINGS_NAME, 'current' ) ) ) {
array_push( $available_methods, self::METHOD_NAME );
}
return $available_methods;
}
);
Email_Wizard_Steps::init();
}
/**
* Adds email provider translatable name
*
* @param array $providers - Array with all currently supported providers and their translated names.
*
* @return array
*
* @since 2.6.0
*/
public static function email_provider_name_translated( array $providers ) {
$providers[ self::METHOD_NAME ] = \esc_html__( 'HOTP (Email)', 'wp-2fa' );
return $providers;
}
/**
* Adds email as a provider
*
* @param array $providers - Array with all currently supported providers.
*
* @return array
*
* @since 2.6.0
*/
public static function email_provider( array $providers ) {
array_push( $providers, self::METHOD_NAME );
return $providers;
}
/**
* Adds the extension default settings to the main plugin settings
*
* @param array $default_settings - array with plugin default settings.
*
* @return array
*
* @since 2.6.0
*/
public static function add_default_settings( array $default_settings ) {
$default_settings[ self::POLICY_SETTINGS_NAME ] = self::POLICY_SETTINGS_NAME;
$default_settings['specify-email_hotp'] = 'specify-email_hotp';
$default_settings['method_help_hotp_intro'] = '<h3>' . __( 'Setting up HOTP (one-time code via email)', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the email address where the one-time code should be sent:', 'wp-2fa' ) . '</p>';
$default_settings['method_help_hotp_help'] = __( 'To complete the 2FA configuration you will be sent a one-time code over email, therefore you should have access to the mailbox of this email address. If you do not receive the email with the one-time code please check your spam folder and contact your administrator', 'wp-2fa' );
$default_settings['method_help_hotp_help_email'] = '<b>' . __( 'IMPORTANT', 'wp-2fa' ) . '</b><p>' . __( 'To ensure you always receive the one-time code whitelist the email address from which the codes are sent. This is {from_email}', 'wp-2fa' ) . '</p>';
$default_settings['method_verification_hotp_pre'] = '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent to your email address to finalize the setup', 'wp-2fa' ) . '</p>';
$default_settings['hotp_reconfigure_intro'] = '<h3>' . __( '{reconfigure_or_configure_capitalized} one-time code over email method', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the email address where the one-time code should be sent.', 'wp-2fa' ) . '</p>';
$default_settings['email-option-label'] = __( 'One-time code via email', 'wp-2fa' );
return $default_settings;
}
/**
* Add extension settings to the loop array
*
* @param array $loop_settings - Currently available settings array.
*
* @return array
*
* @since 2.6.0
*/
public static function settings_loop( array $loop_settings ) {
array_push( $loop_settings, self::POLICY_SETTINGS_NAME );
array_push( $loop_settings, 'specify-email_hotp' );
return $loop_settings;
}
/**
* Extracts the selected value from the global settings (if set), and adds it to the output array
*
* @param array $output - The array with output values.
*
* @return array
*
* @since 2.6.0
*/
public static function return_default_selection( array $output ) {
// No method is enabled, fall back to previous selected one - we don't want to break the logic.
$email_enabled = WP2FA::get_wp2fa_setting( self::POLICY_SETTINGS_NAME );
if ( $email_enabled ) {
$output[ self::POLICY_SETTINGS_NAME ] = $email_enabled;
}
return $output;
}
/**
* Returns the status of the mail method (enabled | disabled)
*
* @since 2.6.0
*
* @return boolean
*/
public static function is_enabled(): bool {
if ( null === self::$email_enabled ) {
self::$email_enabled = empty( Settings::get_role_or_default_setting( 'enable_email', 'current' ) ) ? false : true;
}
return self::$email_enabled;
}
}
}
includes/classes/Admin/Methods/class-backup-codes.php 0000644 00000037615 15075513060 0016634 0 ustar 00 <?php
/**
* Responsible for WP2FA user's backup codes manipulation.
*
* @package wp2fa
* @subpackage methods
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*
* @since 2.6.0
*/
declare(strict_types=1);
namespace WP2FA\Methods;
use WP2FA\WP2FA;
use WP2FA\Admin\Settings_Page;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Authenticator\Authentication;
use WP2FA\Admin\Methods\Traits\Login_Attempts;
/**
* Class for handling backup codes.
*
* @since 0.1-dev
*
* @package WP2FA
*/
if ( ! class_exists( '\WP2FA\Methods\Backup_Codes' ) ) {
/**
* Backup code class, for handling backup code generation and such.
*
* @since 2.6.0
*/
class Backup_Codes {
use Login_Attempts;
/**
* Holds the name of the meta key for the allowed login attempts.
*
* @var string
*
* @since 2.0.0
*/
private static $logging_attempts_meta_key = WP_2FA_PREFIX . 'backup-login-attempts';
/**
* Key used for backup codes.
*
* @var string
*
* @since 2.6.0
*/
public const BACKUP_CODES_META_KEY = 'wp_2fa_backup_codes';
/**
* The number backup codes.
*
* @var int
*
* @since 2.6.0
*/
public const NUMBER_OF_CODES = 10;
/**
* The name of the method.
*
* @var string
*
* @since 2.0.0
*/
public const METHOD_NAME = 'backup_codes';
/**
* The login attempts class.
*
* @var \WP2FA\Admin\Controllers\Login_Attempts
*
* @since 2.0.0
*/
private static $login_attempts = null;
/**
* Holds the status of the backup codes functionality
*
* @var bool[]
*
* @since 2.6.0
*/
private static $backup_codes_enabled = array();
/**
* Default extension settings.
*
* @var array
*
* @since 2.6.0
*/
private static $settings = array(
'backup_codes_enabled' => 'yes',
);
/**
* Inits the backup codes class hooks
*
* @return void
*
* @since 2.6.0
*/
public static function init() {
\add_filter( WP_2FA_PREFIX . 'backup_methods_list', array( __CLASS__, 'add_backup_method' ), 10, 2 );
\add_filter( WP_2FA_PREFIX . 'backup_methods_enabled', array( __CLASS__, 'check_backup_method_for_role' ), 10, 2 );
\add_action( 'wp_ajax_wp2fa_run_ajax_generate_json', array( __CLASS__, 'run_ajax_generate_json' ) );
\add_action( WP_2FA_PREFIX . 'remove_backup_methods_for_user', array( __CLASS__, 'remove_backup_methods_for_user' ) );
\add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'settings_loop' ), 10, 2 );
\add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
\add_filter( WP_2FA_PREFIX . 'providers', array( __CLASS__, 'backup_codes' ) );
\add_filter( WP_2FA_PREFIX . 'providers_translated_names', array( __CLASS__, 'fill_providers_array_with_method_name_translated' ) );
\add_filter( WP_2FA_PREFIX . 'user_enabled_backup_methods', array( __CLASS__, 'method_enabled_for_user' ), 10, 2 );
}
/**
* Generate backup codes.
*
* @param object $user User data.
* @param string $args possible args.
*
* @since 2.6.0
*/
public static function generate_codes( $user, $args = '' ) {
$codes = array();
$codes_hashed = array();
// Check for arguments.
if ( isset( $args['number'] ) ) {
$num_codes = (int) $args['number'];
} else {
$num_codes = self::NUMBER_OF_CODES;
}
// Append or replace (default).
if ( isset( $args['method'] ) && 'append' === $args['method'] ) {
$codes_hashed = (array) \get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
}
for ( $i = 0; $i < $num_codes; ++$i ) {
$code = Authentication::get_code();
$codes_hashed[] = \wp_hash_password( $code );
$codes[] = $code;
unset( $code );
}
\update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $codes_hashed );
// Unhashed.
return $codes;
}
/**
* Fills the array of the enabled backup methods is it is provided for the given user
*
* @param array $array_methods - Array to fill if the method is enabled for user.
* @param \WP_User $user - The user to check for.
*
* @return array
*
* @since 2.6.0
*/
public static function method_enabled_for_user( array $array_methods, $user ): array {
if ( self::is_enabled_for_user( $user ) ) {
$array_methods[ self::METHOD_NAME ] = self::get_translated_name();
}
return $array_methods;
}
/**
* Adds Backup codes as a provider.
*
* @param array $providers - Array with all currently supported providers.
*
* @return array
*
* @since 2.6.0
*/
public static function backup_codes( array $providers ) {
array_push( $providers, self::METHOD_NAME );
return $providers;
}
/**
* Adds Backup code as a provider.
*
* @param array $providers - Array with all currently supported providers and their translated names.
*
* @return array
*
* @since 2.6.0
*/
public static function fill_providers_array_with_method_name_translated( array $providers ) {
$providers[ self::METHOD_NAME ] = self::get_translated_name();
return $providers;
}
/**
* Returns the name of the provider
*
* @return string
*
* @since 2.6.0
*/
public static function get_translated_name(): string {
return esc_html__( 'Backup codes', 'wp-2fa' );
}
/**
* Removes the backup method (user meta key) from the database.
*
* @param \WP_User,int,null $user - The user to remove method for.
*
* @return void
*
* @since 2.5.0
*/
public static function remove_backup_methods_for_user( $user ) {
if ( ! Settings::is_provider_enabled_for_role( User_Helper::get_user_role( $user ), self::get_method_name() ) ) {
\delete_user_meta( $user->ID, self::BACKUP_CODES_META_KEY );
}
}
/**
* Generate codes and check remaining amount for user.
*
* @return void
*
* @since 2.6.0
*/
public static function run_ajax_generate_json() {
$user = wp_get_current_user();
check_ajax_referer( 'wp-2fa-backup-codes-generate-json-' . $user->ID, 'nonce' );
// Setup the return data.
$codes = self::generate_codes( $user );
$count = self::codes_remaining_for_user( $user );
$i18n = array(
'count' => esc_html(
sprintf(
/* translators: %s: count */
_n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'wp-2fa' ),
$count
)
),
/* translators: %s: the site's domain */
'title' => esc_html__( 'Two-Factor Backup Codes for %s', 'wp-2fa' ),
);
// Send the response.
wp_send_json_success(
array(
'codes' => $codes,
'i18n' => $i18n,
)
);
}
/**
* Grab number of unused backup codes within the users position.
*
* @param object $user - User data.
*
* @return int Count of codes.
*
* @since 2.6.0
*/
public static function codes_remaining_for_user( $user ) {
$backup_codes = \get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) {
return count( $backup_codes );
}
return 0;
}
/**
* Validate backup codes.
*
* @param object $user User data.
* @param string $code The code we are checking.
*
* @return bool Is is valid or not.
*
* @since 2.6.0
*/
public static function validate_code( $user, $code ) {
$backup_codes = \get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) {
foreach ( $backup_codes as $code_hashed ) {
if ( \wp_check_password( $code, $code_hashed, $user->ID ) ) {
self::delete_code( $user, $code_hashed );
self::clear_login_attempts( $user );
return true;
}
}
}
self::increase_login_attempts( $user );
return false;
}
/**
* Delete code once its used.
*
* @param object $user User data.
* @param string $code_hashed Code to delete.
*
* @since 2.6.0
*/
public static function delete_code( $user, $code_hashed ) {
$backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
// Delete the current code from the list since it's been used.
$backup_codes = array_flip( $backup_codes );
unset( $backup_codes[ $code_hashed ] );
$backup_codes = array_values( array_flip( $backup_codes ) );
// Update the backup code master list.
\update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $backup_codes );
}
/**
* Add the method to the existing backup methods array.
*
* @param array $backup_methods - Array with the currently supported backup methods.
*
* @since 2.0.0
*/
public static function add_backup_method( array $backup_methods ): array {
return array_merge(
$backup_methods,
array(
self::METHOD_NAME => array(
'wizard-step' => '2fa-wizard-config-backup-codes',
'button_name' => sprintf(
/* translators: URL with more information about the backup codes */
esc_html__( 'Login with a backup code: you will get 10 backup codes and you can use one of them when you need to login and you cannot generate a code from the app. %s', 'wp-2fa' ),
'<a href="https://melapress.com/2fa-backup-codes/" target="_blank">' . esc_html__( 'More information.', 'wp-2fa' ) . '</a>'
),
),
)
);
}
/**
* Changes the global backup methods array - removes the method if it is not enabled.
*
* @param array $backup_methods - Array with all global backup methods.
* @param \WP_User $user - User to check for is that method enabled.
*
* @since 2.0.0
*/
public static function check_backup_method_for_role( array $backup_methods, \WP_User $user ): array {
$enabled = self::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) );
if ( ! $enabled ) {
unset( $backup_methods[ self::METHOD_NAME ] );
}
return $backup_methods;
}
/**
* Returns the name of the method.
*
* @since 2.0.0
*/
public static function get_method_name(): string {
return self::METHOD_NAME;
}
/**
* Checks if the backup codes option is enabled for the role
*
* @param string $role - The role name.
*
* @return bool
*
* @since 2.6.0
*/
public static function are_backup_codes_enabled_for_role( $role = 'global' ) {
$role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
if ( ! isset( self::$backup_codes_enabled[ $role ] ) ) {
self::$backup_codes_enabled[ $role ] = false;
if ( 'global' === $role ) {
$setting_value = Settings::get_role_or_default_setting( self::get_settings_name() );
} else {
$setting_value = Settings::get_role_or_default_setting( self::get_settings_name(), 'current', $role );
}
self::$backup_codes_enabled[ $role ] = Settings_Utils::string_to_bool( $setting_value );
}
return self::$backup_codes_enabled[ $role ];
}
/**
* Checks if the backup codes are enabled for the user.
*
* @param int|\WP_User|null $user - The WP user we should extract the meta data for.
*
* @return bool
*
* @since 2.6.0
*
* @throws \LogicException - can not extract user from the given parameters.
*/
public static function is_enabled_for_user( $user ): bool {
$user = User_Helper::get_user_object( $user );
if ( ! \is_a( $user, '\WP_User' ) ) {
throw new \LogicException( 'Not a proper user object provided!' );
}
$codes_remaining = self::codes_remaining_for_user( $user );
return (bool) $codes_remaining;
}
/**
* Adds settings names to the extraction array - grabs the values and stores them based on names.
*
* @param array $settings - Array with all the settings.
*
* @since 2.6.0
*/
public static function settings_loop( array $settings ): array {
return array_merge( $settings, array_keys( self::$settings ) );
}
/**
* Adds the extension default settings to the main plugin settings.
*
* @param array $default_settings - Array with plugin default settings.
*
* @return array
*
* @since 2.6.0
*/
public static function add_default_settings( array $default_settings ) {
return array_merge( $default_settings, self::$settings );
}
/**
* Returns the method settings name
*
* @return string
*
* @since 2.6.0
*/
public static function get_settings_name(): string {
return \array_key_first( self::$settings );
}
/**
* Returns the method settings default value
*
* @return mixed
*
* @since 2.6.0
*/
public static function get_settings_default_value() {
return \reset( self::$settings );
}
/**
* Validates a backup code.
*
* Backup Codes are single use and are deleted upon a successful validation.
*
* @since 2.6.0
*
* @param \WP_User $user \WP_User object of the logged-in user.
*
* @return boolean
*/
public static function validate_backup_codes( $user ) {
if ( ! isset( $user->ID ) || ! isset( $_REQUEST['wp-2fa-backup-code'] ) ) { //phpcs:ignore
return false;
}
return self::validate_code( $user, \sanitize_text_field( \wp_unslash( $_REQUEST['wp-2fa-backup-code'] ) ) );
}
/**
* Returns the backup codes for the user.
*
* @param \WP_User $user \WP_User - object of the logged-in user.
*
* @return array
*
* @since 2.6.0
*/
public static function get_backup_codes_for_user( $user ): array {
$backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
if ( ! \is_array( $backup_codes ) ) {
return array();
}
return $backup_codes;
}
/**
* Send email with fresh code, or to setup email 2fa.
*
* @param int $user_id User id we want to send the message to.
* @param string $nominated_email_address - The user custom address to use (name of the meta key to check for).
*
* @return bool
*
* @since 2.6.0
*/
public static function send_backup_codes_email( $user_id, $nominated_email_address = 'nominated_email_address' ) {
// If we have a nonce posted, check it.
if ( \wp_doing_ajax() && isset( $_POST['_wpnonce'] ) ) {
$nonce_check = \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ) ), 'wp-2fa-send-backup-codes-email-nonce' );
if ( ! $nonce_check ) {
return false;
}
} else {
\wp_die();
}
$user = User_Helper::get_user_object();
$enabled_email_address = '';
if ( ! empty( $nominated_email_address ) ) {
if ( 'nominated_email_address' === $nominated_email_address ) {
$enabled_email_address = User_Helper::get_nominated_email_for_user( $user );
} else {
$enabled_email_address = get_user_meta( $user->ID, WP_2FA_PREFIX . $nominated_email_address, true );
}
}
if ( isset( $_POST['codes'] ) ) {
$codes = substr( str_replace( '\\n', '<br>', \sanitize_text_field( \wp_unslash( $_POST['codes'] ) ) ), 1, -1 );
$posted_codes = array_filter( \explode( '<br>', $codes ) );
$stored_codes = self::get_backup_codes_for_user( $user );
foreach ( $posted_codes as $key => $check_code ) {
$check_code = trim( \explode( ':', $check_code )[1] );
if ( ! \wp_check_password( $check_code, $stored_codes[ $key ], $user->ID ) ) {
\wp_die();
}
}
} else {
\wp_die();
}
$subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_backup_codes_email_subject' ), $user->ID ) );
$message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_backup_codes_email_body' ), $user->ID ) );
$final_output = str_replace( '{backup_codes}', $codes, $message );
if ( ! empty( $enabled_email_address ) ) {
$email_address = $enabled_email_address;
} else {
$email_address = $user->user_email;
}
return Settings_Page::send_email( $email_address, $subject, $final_output );
}
/**
* Marks methods as secondary.
*
* @return boolean
*
* @since 2.7.0
*/
public static function is_secondary() {
return true;
}
}
}
includes/classes/Admin/Methods/class-totp-wizard-steps.php 0000644 00000036112 15075513060 0017703 0 ustar 00 <?php
/**
* Responsible for WP2FA user's TOTP manipulation.
*
* @package wp2fa
* @subpackage methods-wizard
* @since 2.6.0
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Methods\Wizards;
use WP2FA\WP2FA;
use WP2FA\Methods\TOTP;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Views\Wizard_Steps;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Methods\Traits\Methods_Wizards_Trait;
use WP2FA\Authenticator\Authentication;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
/**
* Class for handling totp codes.
*
* @since 2.6.0
*
* @package WP2FA
*/
if ( ! class_exists( '\WP2FA\Methods\Wizards\TOTP_Wizard_Steps' ) ) {
/**
* TOTP code class, for handling totp (app) code generation and such.
*
* @since 2.6.0
*/
class TOTP_Wizard_Steps extends Wizard_Steps {
use Methods_Wizards_Trait;
/**
* Keeps the main class method name, so we can call it when needed.
*
* @var string
*
* @since 2.6.0
*/
private static $main_class = TOTP::class;
/**
* The default value of the method order in the wizards.
*
* @var integer
*
* @since 2.6.0
*/
private static $order = 1;
/**
* Inits the class hooks
*
* @return void
*
* @since 2.4.0
*/
public static function init() {
\add_filter( WP_2FA_PREFIX . 'methods_modal_options', array( __CLASS__, 'totp_option' ), 10, 2 );
\add_action( WP_2FA_PREFIX . 'modal_methods', array( __CLASS__, 'totp_modal_configure' ) );
\add_filter( WP_2FA_PREFIX . 'methods_re_configure', array( __CLASS__, 'totp_re_configure' ), 10, 2 );
\add_filter( WP_2FA_PREFIX . 'methods_settings', array( __CLASS__, 'totp_wizard_settings' ), 10, 4 );
}
/**
* Shows the option to reconfigure email (if applicable)
*
* @param array $methods - Array of methods collected.
* @param string $role - The name of the role to show option to.
*
* @since 2.6.0
*
* @return array
*/
public static function totp_re_configure( array $methods, string $role ): array {
if ( ! TOTP::is_enabled() ) {
return $methods;
}
\ob_start();
?>
<div class="option-pill">
<?php echo \wp_kses_post( WP2FA::contextual_reconfigure_text( WP2FA::get_wp2fa_white_label_setting( 'totp_reconfigure_intro', true ), User_Helper::get_user_object()->ID, TOTP::METHOD_NAME ) ); ?>
<div class="wp2fa-setup-actions">
<a href="#" class="button button-primary wp-2fa-button-primary" data-name="next_step_setting_modal_wizard" data-trigger-reset-key <?php echo WP_Helper::create_data_nonce( self::json_nonce() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" data-next-step="2fa-wizard-totp"><?php \esc_html_e( 'Reset Key', 'wp-2fa' ); ?></a>
</div>
</div>
<?php
$output = ob_get_contents();
ob_end_clean();
$methods[ self::get_order( $role, $methods ) ] = array(
'name' => self::$main_class::METHOD_NAME,
'output' => $output,
);
return $methods;
}
/**
* Shows the initial totp setup options based on enabled methods
*
* @param array $methods - Array of methods collected.
* @param string $role - The name of the role to show option to.
*
* @since 2.6.0
*
* @return array
*/
public static function totp_option( array $methods, string $role ): array {
if ( TOTP::is_enabled() ) {
\ob_start();
?>
<div class="option-pill">
<label for="basic">
<input id="basic" name="wp_2fa_enabled_methods" type="radio" value="totp">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'totp-option-label', true ) ); ?><span class="wizard-tooltip" data-tooltip-content="data-totp-tooltip-content-wrapper">i</span>
</label>
<?php
echo '<p class="description tooltip-content-wrapper" data-totp-tooltip-content-wrapper>';
echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'totp-option-label-hint', true ) );
echo '</p>';
?>
</div>
<?php
$output = ob_get_contents();
ob_end_clean();
$methods[ self::get_order( $role, $methods ) ] = $output;
}
return $methods;
}
/**
* Shows the TOTP modal configuration.
*
* @return void
*
* @since 2.6.0
*/
public static function totp_modal_configure() {
if ( TOTP::is_enabled() ) {
?>
<div class="wizard-step" id="2fa-wizard-totp">
<fieldset>
<?php self::totp_configure(); ?>
</fieldset>
</div>
<?php
}
}
/**
* Reconfigures the totp form
*
* @since 2.6.0
*
* @return void
*/
public static function totp_configure() {
if ( ! TOTP::is_enabled() ) {
return;
}
// Regenerate the code if the method is not in use.
if ( TOTP::METHOD_NAME !== User_Helper::get_enabled_method_for_user() ) {
TOTP::remove_user_totp_key();
}
/**
* Active on modal, additional attribute is required on standard HTML (check below)
*/
$add_step_attributes = 'active';
/**
* Closing div for extra modal wrappers see lines above
*/
$close_div = '';
$qr_code = '<img class="qr-code" src="' . ( TOTP::get_qr_code() ) . '" id="wp-2fa-totp-qrcode" />';
$open30_wrapper = '
<div class="mb-30 clear-both">
';
$open60_wrapper = '
<div class="modal-60">
';
$open40_wrapper = '
<div class="modal-40">
';
$close_div = '
</div>
';
?>
<div class="step-setting-wrapper <?php echo \esc_attr( $add_step_attributes ); ?>">
<div class="mb-20">
<?php echo wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_intro', true ) ); ?>
</div>
<?php echo $open30_wrapper . $open40_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class="qr-code-wrapper">
<?php echo $qr_code; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php
echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $open60_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<div class="radio-cells option-pill mb-0">
<ol class="wizard-custom-counter">
<li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_1', true ) ); ?>
<?php
if ( ! empty( WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) ) {
?>
<span class="wizard-tooltip" data-tooltip-content="data-totp-setup-tooltip-content-wrapper">i</span><?php } ?></li>
<li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_2', true ) ); ?>
<div class="app-key-wrapper">
<input type="text" id="app-key-input" readonly value="<?php echo \esc_html( TOTP::get_totp_decrypted() ); ?>" class="app-key">
<?php
if ( is_ssl() ) {
?>
<span class="click-to-copy"><?php \esc_html_e( 'COPY', 'wp-2fa' ); ?></span>
<?php } ?>
</div>
</li>
<li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_3', true ) ); ?></li>
</ol>
</div>
<?php
echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<?php if ( ! empty( WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) ) : ?>
<div class="tooltip-content-wrapper" data-totp-setup-tooltip-content-wrapper>
<p class="description"><?php \esc_html_e( 'Click on the icon of the app that you are using for a detailed guide on how to set it up.', 'wp-2fa' ); ?></p>
<div class="apps-wrapper">
<?php foreach ( Authentication::get_apps() as $app ) { ?>
<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa#<?php echo $app['hash']; ?>" target="_blank" class="app-logo"><img src="<?php echo \esc_url( WP_2FA_URL . 'dist/images/' . $app['logo'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"></a>
<?php } ?>
</div>
</div>
<?php endif; ?>
<div class="wp2fa-setup-actions">
<button class="button wp-2fa-button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" type="button"><?php \esc_html_e( 'I\'m Ready', 'wp-2fa' ); ?></button>
<a class="button button-primary wp-2fa-button-secondary modal_cancel"><?php \esc_attr_e( 'Cancel', 'wp-2fa' ); ?></a>
</div>
</div>
<div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Verify configuration', 'wp-2fa' ); ?>">
<div class="mb-20">
<?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_verification_totp_pre', true ) ); ?>
</div>
<fieldset>
<label for="2fa-totp-authcode">
<?php \esc_html_e( 'Authentication Code', 'wp-2fa' ); ?>
<input type="tel" name="wp-2fa-totp-authcode" id="wp-2fa-totp-authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off"/>
<script>
const totp_authcode = document.getElementById('wp-2fa-totp-authcode');
totp_authcode.addEventListener('input', function() {
this.value = this.value.trim();
});
</script>
</label>
<div class="verification-response"></div>
</fieldset>
<input type="hidden" name="wp-2fa-totp-key" value="<?php echo \esc_attr( TOTP::get_totp_decrypted() ); ?>" />
<a href="#" class="modal__btn button button-primary wp-2fa-button-primary" data-validate-authcode-ajax <?php echo WP_Helper::create_data_nonce( 'wp-2fa-validate-authcode' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php \esc_html_e( 'Validate & Save', 'wp-2fa' ); ?></a>
<button class="modal__btn wp-2fa-button-secondary button button-secondary wp-2fa-button-secondary" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
</div>
<?php
}
/**
* Settings page and first time wizard settings render
*
* @param array $methods - Array with all the methods in which we have to add this one.
* @param boolean $setup_wizard - Is that the first time setup wizard.
* @param string $data_role - Additional HTML data attribute.
* @param mixed $role - Name of the role.
*
* @return array - The array with the methods with all the methods wizard steps.
*
* @since 2.6.0
*/
public static function totp_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) {
$name_prefix = WP_2FA_POLICY_SETTINGS_NAME;
$role_id = '';
if ( null !== $role && '' !== trim( (string) $role ) ) {
$name_prefix .= "[{$role}]";
$data_role = 'data-role="' . $role . '"';
$role_id = '-' . $role;
}
\ob_start();
?>
<div id="<?php echo \esc_attr( TOTP::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper">
<?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<label for="totp<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;">
<input type="checkbox" id="totp<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_totp]" value="enable_totp"
<?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if ( null !== $role && ! empty( $role ) ) { ?>
<?php \checked( TOTP::POLICY_SETTINGS_NAME, Role_Settings_Controller::get_setting( $role, TOTP::POLICY_SETTINGS_NAME ), true ); ?>
<?php
} else {
$use_role_setting = null;
if ( null === $role || '' === trim( (string) $role ) ) {
$use_role_setting = \WP_2FA_PREFIX . 'no-user';
}
$enabled_settings = Settings::get_role_or_default_setting( TOTP::POLICY_SETTINGS_NAME, $use_role_setting, $role, true );
?>
<?php \checked( $enabled_settings, TOTP::POLICY_SETTINGS_NAME ); ?>
<?php } ?>
>
<?php \esc_html_e( 'One-time code via 2FA App (TOTP) - ', 'wp-2fa' ); ?><a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank" rel=noopener><?php \esc_html_e( 'complete list of supported 2FA apps.', 'wp-2fa' ); ?></a>
</label>
<?php
if ( $setup_wizard ) {
echo '<p class="description">';
printf(
/* translators: link to the knowledge base website */
\esc_html__( 'When using this method, users will need to configure a 2FA app to get the one-time login code. The plugin supports all standard 2FA apps. Refer to the %s for more information. Allowing users to set up a secondary 2FA method is highly recommended. You can do this in the next step of the wizard. This will allow users to log in using an alternative method should they, for example lose access to their phone.', 'wp-2fa' ),
'<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>'
);
echo '</p>';
}
if ( ! $setup_wizard ) {
echo '<p class="description">';
printf(
/* translators: link to the knowledge base website */
\esc_html__( 'Refer to the %s for more information on how to setup these apps and which apps are supported.', 'wp-2fa' ),
'<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>'
);
echo '</p>';
}
?>
</div>
<?php
$output = ob_get_contents();
ob_end_clean();
$methods[ self::get_order( $role, $methods ) ] = $output;
return $methods;
}
/**
* Prints the form that prompts the user to authenticate.
*
* @param \WP_User $user - \WP_User object of the logged-in user.
*
* @since 2.6.0
*/
public static function totp_authentication_page( $user ) {
require_once ABSPATH . '/wp-admin/includes/template.php';
?>
<?php
if ( 'use-custom' == WP2FA::get_wp2fa_white_label_setting( 'use_custom_2fa_message' ) ) {
echo WP2FA::get_wp2fa_white_label_setting( 'custom-text-app-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo WP2FA::get_wp2fa_white_label_setting( 'default-text-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
<p>
</br>
<label for="authcode"><?php \esc_html_e( 'Authentication Code:', 'wp-2fa' ); ?></label>
<input type="tel" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off" />
<script>
const authcode = document.getElementById('authcode');
authcode.addEventListener('input', function() {
this.value = this.value.trim();
});
</script>
</p>
<?php
}
}
} includes/classes/Admin/Methods/index.php 0000644 00000000046 15075513060 0014264 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/Admin/class-help-contact-us.php 0000644 00000042511 15075513060 0015666 0 ustar 00 <?php
/**
* Contact us and help rendering class.
*
* @package wp2fa
* @subpackage admin
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
* @since 2.0.0
*/
namespace WP2FA\Admin;
use WP2FA\Admin\Settings_Page;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\WP2FA;
if ( ! class_exists( '\WP2FA\Admin\Help_Contact_Us' ) ) {
/**
* Handles contact us tab and content.
*/
class Help_Contact_Us {
const TOP_MENU_SLUG = 'wp-2fa-help-contact-us';
/**
* Create admin menu entry and settings page
*/
public static function add_extra_menu_item() {
add_submenu_page(
Settings_Page::TOP_MENU_SLUG,
\esc_html__( 'Help & Contact Us', 'wp-2fa' ),
\esc_html__( 'Help & Contact Us', 'wp-2fa' ),
'manage_options',
self::TOP_MENU_SLUG,
array( __CLASS__, 'render' ),
100
);
}
/**
* Handles rendering the help tabs and their wrapping element.
*
* @return void
*/
public static function render() {
$main_user = get_current_user_id();
$current_user_id = $main_user;
if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
$main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
}
?>
<?php if ( ! empty( WP2FA::get_wp2fa_general_setting( 'limit_access' ) ) && $main_user !== $current_user_id ) : ?>
</br>
<?php
echo \esc_html__( 'These settings have been disabled by your site administrator, please contact them for further assistance.', 'wp-2fa' );
?>
<?php else : ?>
<div class="wrap help-wrap">
<h2><?php \esc_html_e( 'Help', 'wp-2fa' ); ?></h2>
<hr>
<br>
<div class="nav-tab-wrapper">
<?php
// Get current tab.
$current_tab = isset( $_GET['tab'] ) ? \sanitize_text_field( \wp_unslash( $_GET['tab'] ) ) : 'help'; // phpcs:ignore
?>
<a href="<?php echo \esc_url( remove_query_arg( 'tab' ) ); ?>" class="nav-tab<?php echo 'help' === $current_tab ? ' nav-tab-active' : ''; ?>"><?php \esc_html_e( 'Help', 'wp-2fa' ); ?></a>
<a href="<?php echo \esc_url( add_query_arg( 'tab', 'system-info' ) ); ?>" class="nav-tab<?php echo 'system-info' === $current_tab ? ' nav-tab-active' : ''; ?>"><?php \esc_html_e( 'System info', 'wp-2fa' ); ?></a>
</div>
<div class="wp2fa-help-section nav-tabs">
<?php
self::sidebar();
if ( 'help' === $current_tab ) {
self::help();
} elseif ( 'system-info' === $current_tab ) {
self::system_info();
}
?>
</div>
</div>
<?php endif; ?>
<?php
}
/**
* Help tab content.
*
* @return void
*/
public static function help() {
?>
<div class="wp2fa-help-main">
<!-- getting started -->
<div class="title">
<h2><?php \esc_html_e( 'Getting started', 'wp-2fa' ); ?></h2>
</div>
<p><?php \esc_html_e( 'Getting started with WP 2FA and making 2FA compulsory is as easy as 1 2 3 with WP 2FA. This can be easily done through the install wizard or the plugin settings. If you are stuck, no problem! Below are a few links of guides to help you get started:', 'wp-2fa' ); ?></p>
<ul>
<li><?php echo wp_sprintf( '<a href="%1$s" target="_blank">%2$s</a>', \esc_url( 'https://melapress.com/support/kb/wp-2fa-plugin-getting-started/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ), \esc_html__( 'Getting started with WP 2FA', 'wp-2fa' ) ); ?></li>
<li><?php echo wp_sprintf( '<a href="%1$s" target="_blank">%2$s</a>', \esc_url( 'https://melapress.com/support/kb/wp-2fa-configure-2fa-policies-enforce/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ), \esc_html__( 'Configuring 2FA policies & making 2FA mandatory', 'wp-2fa' ) ); ?></li>
<li><?php echo wp_sprintf( '<a href="%1$s" target="_blank">%2$s</a>', \esc_url( 'https://melapress.com/support/kb/wp-2fa-configure-2fa-front-end-page-wordpress/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ), \esc_html__( 'Allowing users to configure 2FA from a website page (no dashboard access)', 'wp-2fa' ) ); ?></li>
</ul>
<!-- End -->
<br>
<p><iframe title="<?php \esc_html_e( 'Getting started', 'wp-2fa' ); ?>" class="wsal-youtube-embed" width="100%" height="315" src="https://www.youtube.com/embed/vRlX_NNGeFo" frameborder="0" allowfullscreen></iframe></p>
<!-- Plugin documentation -->
<div class="title">
<h2><?php \esc_html_e( 'Plugin documentation', 'wp-2fa' ); ?></h2>
</div>
<p><?php \esc_html_e( 'For more technical information about the WP 2FA plugin please visit the plugin\'s knowledge base.', 'wp-2fa' ); ?></p>
<div class="btn">
<a href="<?php echo \esc_url( 'https://melapress.com/support/kb/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ); ?>" class="button" target="_blank"><?php \esc_html_e( 'Knowledge base', 'wp-2fa' ); ?></a>
</div>
<!-- End -->
<!-- Plugin support -->
<div class="title">
<h2><?php \esc_html_e( 'Plugin support', 'wp-2fa' ); ?></h2>
</div>
<p><?php \esc_html_e( 'Do you need assistance with the plugin? Have you noticed or encountered an issue while using WP 2FA, or do you just want to report something to us?', 'wp-2fa' ); ?></p>
<div class="btn">
<a href="<?php echo \esc_url( 'https://melapress.com/support/submit-ticket/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ); ?>" class="button" target="_blank"><?php \esc_html_e( 'Open support ticket', 'wp-2fa' ); ?></a>
<a href="<?php echo \esc_url( 'https://melapress.com/contact/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa' ); ?>" class="button" target="_blank"><?php \esc_html_e( 'Contact us', 'wp-2fa' ); ?></a>
</div>
<!-- End -->
</div>
<?php
}
/**
* System info tab content.
*
* @return void
*/
public static function system_info() {
?>
<div class="wp2fa-help-main">
<!-- getting started -->
<div class="title">
<h2><?php \esc_html_e( 'System information', 'wp-2fa' ); ?></h2>
</div>
<form method="post" dir="ltr">
<textarea readonly="readonly" onclick="this.focus(); this.select()" id="system-info-textarea" name="wsal-sysinfo"><?php echo self::get_sysinfo(); // phpcs:ignore ?></textarea>
<p class="submit">
<input type="hidden" name="ppmwp-action" value="download_sysinfo" />
<?php submit_button( 'Download System Info File', 'primary', 'wp2fa-download-sysinfo', false ); ?>
</p>
</form>
<script>
function download(filename, text) {
// Create temporary element.
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
// Set the element to not display.
element.style.display = 'none';
document.body.appendChild(element);
// Simlate click on the element.
element.click();
// Remove temporary element.
document.body.removeChild(element);
}
jQuery( document ).ready( function() {
var download_btn = jQuery( '#wp2fa-download-sysinfo' );
download_btn.click( function( event ) {
event.preventDefault();
download( 'wp2fa-system-info.txt', jQuery( '#system-info-textarea' ).val() );
} );
} );
</script>
</div>
<?php
}
/**
* Advertising sidebar.
*
* @return void
*/
public static function sidebar() {
?>
<div class="our-wordpress-plugins side-bar">
<h3><?php \esc_html_e( 'Our WordPress Plugins', 'wp-2fa' ); ?></h3>
<ul>
<li>
<div class="plugin-box">
<div class="plugin-img">
<img src="<?php echo WP_2FA_URL; // phpcs:ignore ?>dist/images/wp-activity-log.jpeg" alt="">
</div>
<div class="plugin-desc">
<p><?php \esc_html_e( 'Keep a log of users and under the hood site activity.', 'wp-2fa' ); ?></p>
<div class="cta-btn">
<a href="
<?php
echo \esc_url(
add_query_arg(
array(
'utm_source' => 'plugin',
'utm_medium' => 'referral',
'utm_campaign' => 'WSAL',
'utm_content' => 'WP2FA+banner',
),
'https://melapress.com/wordpress-activity-log/'
)
);
?>
" target="_blank"><?php \esc_html_e( 'LEARN MORE', 'wp-2fa' ); ?></a>
</div>
</div>
</div>
</li>
<li>
<div class="plugin-box">
<div class="plugin-img">
<img src="<?php echo WP_2FA_URL; // phpcs:ignore ?>dist/images/login-security.jpeg" alt="">
</div>
<div class="plugin-desc">
<p><?php \esc_html_e( 'Enforce strong password policies on WordPress.', 'wp-2fa' ); ?></p>
<div class="cta-btn">
<a href="
<?php
echo \esc_url(
add_query_arg(
array(
'utm_source' => 'plugin',
'utm_medium' => 'referral',
'utm_campaign' => 'WSAL',
'utm_content' => 'WP2FA+banner',
),
'https://melapress.com/wordpress-login-security/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa'
)
);
?>
" target="_blank"><?php \esc_html_e( 'LEARN MORE', 'wp-2fa' ); ?></a>
</div>
</div>
</div>
</li>
<li>
<div class="plugin-box">
<div class="plugin-img">
<img src="<?php echo WP_2FA_URL; // phpcs:ignore ?>dist/images/website-file-changes-monitor.jpg" alt="">
</div>
<div class="plugin-desc">
<p><?php \esc_html_e( 'Automatically identify unauthorized file changes on your WordPress site.', 'wp-2fa' ); ?></p>
<div class="cta-btn">
<a href="
<?php
echo \esc_url(
add_query_arg(
array(
'utm_source' => 'plugin',
'utm_medium' => 'referral',
'utm_campaign' => 'WSAL',
'utm_content' => 'WP2FA+banner',
),
'https://melapress.com/wordpress-plugins/website-file-changes-monitor/'
)
);
?>
" target="_blank"><?php \esc_html_e( 'LEARN MORE', 'wp-2fa' ); ?></a>
</div>
</div>
</div>
</li>
<li>
<div class="plugin-box">
<div class="plugin-img">
<img src="<?php echo WP_2FA_URL; // phpcs:ignore ?>dist/images/c4wp.jpg" alt="">
</div>
<div class="plugin-desc">
<p><?php \esc_html_e( 'Protect website forms & login pages from spam bots & automated attacks.', 'wp-2fa' ); ?></p>
<div class="cta-btn">
<a href="
<?php
echo \esc_url(
add_query_arg(
array(
'utm_source' => 'plugin',
'utm_medium' => 'referral',
'utm_campaign' => 'WSAL',
'utm_content' => 'WP2FA+banner',
),
'https://melapress.com/wordpress-captcha/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa'
)
);
?>
" target="_blank"><?php \esc_html_e( 'LEARN MORE', 'wp-2fa' ); ?></a>
</div>
</div>
</div>
</li>
</ul>
</div>
<?php
}
/**
* Gather basic settings and system information for use in the system info tab. Left untranslated (as is the case in all plugins)
* as its for our use only.
*
* @return string
*/
public static function get_sysinfo() {
// System info.
global $wpdb;
$sysinfo = '### System Info → Begin ###' . "\n\n";
// Start with the basics...
$sysinfo .= '-- Site Info --' . "\n\n";
$sysinfo .= 'Site URL (WP Address): ' . site_url() . "\n";
$sysinfo .= 'Home URL (Site Address): ' . home_url() . "\n";
$sysinfo .= 'Multisite: ' . ( WP_Helper::is_multisite() ? 'Yes' : 'No' ) . "\n";
// Get theme info.
$theme_data = wp_get_theme();
$theme = $theme_data->name . ' ' . $theme_data->version;
$parent_theme = $theme_data->template;
if ( ! empty( $parent_theme ) ) {
$parent_theme_data = wp_get_theme( $parent_theme );
$parent_theme = $parent_theme_data->name . ' ' . $parent_theme_data->version;
}
// Language information.
$locale = get_locale();
// WordPress configuration.
$sysinfo .= "\n" . '-- WordPress Configuration --' . "\n\n";
$sysinfo .= 'Version: ' . get_bloginfo( 'version' ) . "\n";
$sysinfo .= 'Language: ' . ( ! empty( $locale ) ? $locale : 'en_US' ) . "\n";
$sysinfo .= 'Permalink Structure: ' . ( get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default' ) . "\n";
$sysinfo .= 'Active Theme: ' . $theme . "\n";
if ( $parent_theme !== $theme ) {
$sysinfo .= 'Parent Theme: ' . $parent_theme . "\n";
}
$sysinfo .= 'Show On Front: ' . get_option( 'show_on_front' ) . "\n";
// Only show page specs if frontpage is set to 'page'.
if ( 'page' === get_option( 'show_on_front' ) ) {
$front_page_id = (int) get_option( 'page_on_front' );
$blog_page_id = (int) get_option( 'page_for_posts' );
$sysinfo .= 'Page On Front: ' . ( 0 !== $front_page_id ? get_the_title( $front_page_id ) . ' (#' . $front_page_id . ')' : 'Unset' ) . "\n";
$sysinfo .= 'Page For Posts: ' . ( 0 !== $blog_page_id ? get_the_title( $blog_page_id ) . ' (#' . $blog_page_id . ')' : 'Unset' ) . "\n";
}
$sysinfo .= 'ABSPATH: ' . ABSPATH . "\n";
$sysinfo .= 'WP_DEBUG: ' . ( defined( 'WP_DEBUG' ) ? ( WP_DEBUG ? 'Enabled' : 'Disabled' ) : 'Not set' ) . "\n";
$sysinfo .= 'WP Memory Limit: ' . WP_MEMORY_LIMIT . "\n";
// Get plugins that have an update.
$updates = get_plugin_updates();
// Must-use plugins.
// NOTE: MU plugins can't show updates!
$muplugins = get_mu_plugins();
if ( count( $muplugins ) > 0 ) {
$sysinfo .= "\n" . '-- Must-Use Plugins --' . "\n\n";
foreach ( $muplugins as $plugin => $plugin_data ) {
$sysinfo .= $plugin_data['Name'] . ': ' . $plugin_data['Version'] . "\n";
}
}
// WordPress active plugins.
$sysinfo .= "\n" . '-- WordPress Active Plugins --' . "\n\n";
$plugins = get_plugins();
$active_plugins = get_option( 'active_plugins', array() );
foreach ( $plugins as $plugin_path => $plugin ) {
if ( ! in_array( $plugin_path, $active_plugins, true ) ) {
continue;
}
$update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : '';
$sysinfo .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n";
}
// WordPress inactive plugins.
$sysinfo .= "\n" . '-- WordPress Inactive Plugins --' . "\n\n";
foreach ( $plugins as $plugin_path => $plugin ) {
if ( in_array( $plugin_path, $active_plugins, true ) ) {
continue;
}
$update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : '';
$sysinfo .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n";
}
if ( WP_Helper::is_multisite() ) {
// WordPress Multisite active plugins.
$sysinfo .= "\n" . '-- Network Active Plugins --' . "\n\n";
$plugins = wp_get_active_network_plugins();
$active_plugins = get_site_option( 'active_sitewide_plugins', array() );
foreach ( $plugins as $plugin_path ) {
$plugin_base = plugin_basename( $plugin_path );
if ( ! array_key_exists( $plugin_base, $active_plugins ) ) {
continue;
}
$update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : '';
$plugin = get_plugin_data( $plugin_path );
$sysinfo .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n";
}
}
// Server configuration.
$server_software = ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) ? \sanitize_text_field( \wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '';
$sysinfo .= "\n" . '-- Webserver Configuration --' . "\n\n";
$sysinfo .= 'PHP Version: ' . PHP_VERSION . "\n";
$sysinfo .= 'MySQL Version: ' . $wpdb->db_version() . "\n";
if ( isset( $server_software ) ) {
$sysinfo .= 'Webserver Info: ' . $server_software . "\n";
} else {
$sysinfo .= 'Webserver Info: Global $_SERVER array is not set.' . "\n";
}
// PHP configs.
$sysinfo .= "\n" . '-- PHP Configuration --' . "\n\n";
$sysinfo .= 'Memory Limit: ' . ini_get( 'memory_limit' ) . "\n";
$sysinfo .= 'Upload Max Size: ' . ini_get( 'upload_max_filesize' ) . "\n";
$sysinfo .= 'Post Max Size: ' . ini_get( 'post_max_size' ) . "\n";
$sysinfo .= 'Upload Max Filesize: ' . ini_get( 'upload_max_filesize' ) . "\n";
$sysinfo .= 'Time Limit: ' . ini_get( 'max_execution_time' ) . "\n";
$sysinfo .= 'Max Input Vars: ' . ini_get( 'max_input_vars' ) . "\n";
$sysinfo .= 'Display Errors: ' . ( ini_get( 'display_errors' ) ? 'On (' . ini_get( 'display_errors' ) . ')' : 'N/A' ) . "\n";
$sysinfo .= "\n" . '-- WP 2FA Settings --' . "\n\n";
global $wpdb;
$wp2fa_options = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'wp_2fa_%'", ARRAY_A ); // phpcs:ignore
if ( ! empty( $wp2fa_options ) ) {
foreach ( $wp2fa_options as $option => $value ) {
$sysinfo .= 'Option: ' . $value['option_name'] . "\n";
$sysinfo .= 'Value: ' . print_r( $value['option_value'], true ) . "\n\n"; // phpcs:ignore
}
}
$sysinfo .= "\n" . '### System Info → End ###' . "\n\n";
return $sysinfo;
}
}
}
includes/classes/Admin/class-setup-wizard.php 0000644 00000057060 15075513060 0015323 0 ustar 00 <?php
/**
* Setup wizard rendering class.
*
* @package wp2fa
* @subpackage setup
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA\Admin;
use WP2FA\Core;
use WP2FA\WP2FA;
use WP2FA\Methods\TOTP;
use WP2FA\Methods\Email;
use WP2FA\Utils\User_Utils;
use WP2FA\Admin\Settings_Page;
use WP2FA\Methods\Backup_Codes;
use WP2FA\Utils\Generate_Modal;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Views\Re_Login_2FA;
use WP2FA\Admin\Views\Wizard_Steps;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Authenticator\Authentication;
use WP2FA\Admin\Views\First_Time_Wizard_Steps;
use WP2FA\Admin\SettingsPages\Settings_Page_Policies;
/**
* Setup_Wizard class for the wizard steps setup
*
* @since 2.4.0
*/
if ( ! class_exists( '\WP2FA\Admin\Setup_Wizard' ) ) {
/**
* Our class for creating a step by step wizard for easy configuration.
*/
class Setup_Wizard {
/**
* Wizard Steps
*
* @var array
*
* @since 2.8.0
*/
private static $wizard_steps;
/**
* Current Step
*
* @var string
*
* @since 2.8.0
*/
private static $current_step;
/**
* Add setup admin page. This is empty on purpose.
*
* @since 2.8.0
*/
public static function admin_menus() {
\add_dashboard_page( '', '', 'read', 'wp-2fa-setup', '' );
}
/**
* Adding menus for multisite install
*
* @return void
*
* @since 2.2.0
*/
public static function network_admin_menus() {
\add_dashboard_page( 'index.php', '', 'read', 'wp-2fa-setup', '' );
}
/**
* Setup Page Start.
*
* @since 2.8.0
*/
public static function setup_page() {
// Get page argument from $_GET array.
$page = ( isset( $_GET['page'] ) ) ? \sanitize_text_field( \wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore
if ( empty( $page ) || 'wp-2fa-setup' !== $page ) {
return;
}
// Clear out any old notices.
$user = \wp_get_current_user();
// First lets check if any options have been saved.
$settings_saved = true;
$settings = WP2FA::get_wp2fa_setting();
if ( empty( $settings ) || ! isset( $settings ) ) {
$settings_saved = false;
}
if ( Settings_Utils::get_option( 'wizard_not_finished' ) ) {
$settings_saved = false;
}
/**
* Wizard Steps.
*/
$get_array = filter_input_array( INPUT_GET );
if ( isset( $get_array['wizard_type'] ) ) {
$wizard_type = \sanitize_text_field( $get_array['wizard_type'] );
} else {
$wizard_type = 'default';
}
$is_user_forced_to_setup = User_Helper::get_user_enforced_instantly( $user );
if ( ! empty( $is_user_forced_to_setup ) ) {
\add_filter( 'wp_2fa_wizard_default_steps', array( __CLASS__, 'wp_2fa_add_intro_step' ) );
}
$user_type = User_Utils::determine_user_2fa_status( $user );
$wizard_steps = array(
'welcome' => array(
'name' => \esc_html__( 'Welcome', 'wp-2fa' ),
'content' => array( __CLASS__, 'wp_2fa_step_welcome' ),
'wizard_type' => 'welcome_wizard',
),
'settings_configuration' => array(
'name' => \esc_html__( 'Configure 2FA methods & Policies', 'wp-2fa' ),
'content' => array( __CLASS__, 'wp_2fa_step_global_2fa_methods' ),
'save' => array( __CLASS__, 'wp_2fa_step_global_2fa_methods_save' ),
'wizard_type' => 'welcome_wizard',
),
'finish' => array(
'name' => \esc_html__( 'Setup Finish', 'wp-2fa' ),
'content' => array( __CLASS__, 'wp_2fa_step_finish' ),
'save' => array( __CLASS__, 'wp_2fa_step_finish_save' ),
'wizard_type' => 'welcome_wizard',
),
);
// Admin user setting up fresh install of 2FA plugin.
if ( in_array( 'can_manage_options', $user_type, true ) && ! $settings_saved ) {
unset( $wizard_steps['user_choose_2fa_method'] );
unset( $wizard_steps['reconfigure_method'] );
}
// We will use this setting to determine if defaults have already been saved to the DB.
$have_defaults_been_applied = Settings_Utils::get_option( 'default_settings_applied', false );
// If we have settings, but they are the defaults, then we want to consider the settings to be unsaved at this point.
if ( in_array( 'can_manage_options', $user_type, true ) && $settings_saved && $have_defaults_been_applied ) {
$settings_saved = false;
}
// Ensure user has minimum capabilities needed to be here.
if ( in_array( 'can_read', $user_type, true ) && $settings_saved ) {
switch ( $wizard_type ) {
case 'user_2fa_config':
$wizard_steps = array_intersect_key( $wizard_steps, array_flip( array( 'user_choose_2fa_method', 'setup_method', 'finish', 'backup_codes' ) ) );
break;
case 'backup_codes_config':
$wizard_steps = array_intersect_key( $wizard_steps, array_flip( array( 'backup_codes' ) ) );
break;
case 'user_reconfigure_config':
$wizard_steps = array_intersect_key( $wizard_steps, array_flip( array( 'reconfigure_method' ) ) );
break;
default:
$wizard_steps = array_intersect_key( $wizard_steps, array_flip( array( 'choose_2fa_method', 'setup_method', 'finish', 'backup_codes', 'reconfigure_method' ) ) );
}
// Remove 1st step if only one method is available.
if ( empty( WP2FA::get_wp2fa_setting( TOTP::POLICY_SETTINGS_NAME ) ) || empty( WP2FA::get_wp2fa_setting( Email::POLICY_SETTINGS_NAME ) ) ) {
unset( $wizard_steps['choose_2fa_method'] );
}
// If the user has codes setup already, no need to add the slide.
if ( ! in_array( 'user_needs_to_setup_backup_codes', $user_type, true ) && 'backup_codes_config' !== $wizard_type ) {
unset( $wizard_steps['backup_codes'] );
}
}
/**
* Filter: `Wizard Default Steps`
*
* Filter to filter wizard steps before they are displayed.
*
* @param array $wizard_steps – Wizard Steps.
*
* @since 2.8.0
*/
self::$wizard_steps = apply_filters( WP_2FA_PREFIX . 'wizard_default_steps', $wizard_steps );
// Set current step.
$current_step = ( isset( $_GET['current-step'] ) ) ? \sanitize_text_field( \wp_unslash( $_GET['current-step'] ) ) : ''; // phpcs:ignore
self::$current_step = ! empty( $current_step ) ? $current_step : current( array_keys( self::$wizard_steps ) );
if ( Backup_Codes::METHOD_NAME === self::$current_step && ! Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) ) {
$redirect_to_finish = add_query_arg(
array(
'current-step' => 'finish',
'all-set' => 1,
)
);
\wp_safe_redirect( \esc_url_raw( $redirect_to_finish ) );
}
/**
* Enqueue Scripts.
*/
\wp_enqueue_style(
'wp_2fa_setup_wizard',
Core\style_url( 'setup-wizard', 'admin' ),
array( 'select2' ),
WP_2FA_VERSION
);
\wp_enqueue_style(
'wp_2fa_admin-style',
Core\style_url( 'admin-style', 'admin' ),
array(),
WP_2FA_VERSION
);
\WP2FA\Core\enqueue_select2_scripts();
\wp_enqueue_script(
'wp_2fa_admin',
Core\script_url( 'admin', 'admin' ),
array( 'jquery-ui-widget', 'jquery-ui-core', 'jquery-ui-autocomplete', 'select2' ),
WP_2FA_VERSION,
true
);
\wp_enqueue_script(
'wp_2fa_micromodal',
Core\script_url( 'micromodal', 'admin', 'select2' ),
array(),
WP_2FA_VERSION,
true
);
// Data array.
$data_array = array(
'ajaxURL' => \admin_url( 'admin-ajax.php' ),
'roles' => WP_Helper::get_roles_wp(),
'nonce' => \wp_create_nonce( 'wp-2fa-settings-nonce' ),
'invalidEmail ' => \esc_html__( 'Please use a valid email address', 'wp-2fa' ),
'backupCodesSent' => \esc_html__( 'Backup codes sent', 'wp-2fa' ),
);
\wp_localize_script( 'wp_2fa_admin', 'wp2faData', $data_array );
$re_login = Settings::get_role_or_default_setting( Re_Login_2FA::RE_LOGIN_SETTINGS_NAME, 'current', User_Helper::get_user_role() );
// Data array.
$data_array = array(
'ajaxURL' => \admin_url( 'admin-ajax.php' ),
'nonce' => \wp_create_nonce( 'wp2fa-verify-wizard-page' ),
'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ),
'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ),
'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ),
'reLogin' => $re_login,
'reLoginEnabled' => Re_Login_2FA::ENABLED_SETTING_VALUE,
);
/**
* Gives the ability to change the default JS wizard settings.
*
* @param int $data_array - The array with all the JS wizard settings.
*
* @since 2.2.0
*/
$data_array = apply_filters( WP_2FA_PREFIX . 'js_wizard_settings', $data_array );
\wp_localize_script( 'wp_2fa_admin', 'wp2faWizardData', $data_array );
/**
* Save Wizard Settings.
*/
$save_step = ( isset( $_POST['save_step'] ) ) ? \sanitize_text_field( \wp_unslash( $_POST['save_step'] ) ) : ''; // phpcs:ignore
if ( ! empty( $save_step ) && ! empty( self::$wizard_steps[ self::$current_step ]['save'] ) ) {
call_user_func( self::$wizard_steps[ self::$current_step ]['save'] );
}
self::setup_page_header();
self::setup_page_steps();
self::setup_page_content();
self::setup_page_footer();
exit();
}
/**
* Setup Page Header.
*
* @since 2.8.0
*/
private static function setup_page_header() {
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php \esc_html_e( 'WP 2FA › Setup Wizard', 'wp-2fa' ); ?></title>
<?php \wp_print_scripts( 'jquery' ); ?>
<?php \wp_print_scripts( 'jquery-ui-core' ); ?>
<?php \wp_print_scripts( 'wp_2fa_setup_wizard' ); ?>
<?php \wp_print_scripts( 'wp_2fa_micromodal' ); ?>
<?php \wp_print_scripts( 'wp_2fa_admin' ); ?>
<?php
/**
* Gives the ability for 3rd party scripts to add their own JS to the plugin setup page.
*
* @since 2.2.0
*/
\do_action( WP_2FA_PREFIX . 'setup_page_scripts' );
?>
<?php \wp_print_styles( 'common' ); ?>
<?php \wp_print_styles( 'forms' ); ?>
<?php \wp_print_styles( 'buttons' ); ?>
<?php \wp_print_styles( 'wp-jquery-ui-dialog' ); ?>
<?php \wp_print_styles( 'wp_2fa_admin' ); ?>
<?php \do_action( 'admin_print_styles' ); ?>
</head>
<body class="wp2fa-setup wp-core-ui">
<div class="setup-wizard-wrapper wp-2fa-settings-wrapper wp2fa-form-styles">
<h1 id="wp2fa-logo"><a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank"><img style="max-width: 80px;" src="<?php echo \esc_url( WP_2FA_URL . 'dist/images/wp-2fa-color_opt.png' ); ?>"></a></h1>
<?php
}
/**
* Setup Page Footer.
*
* @since 2.8.0
*/
private static function setup_page_footer() {
$user = \wp_get_current_user();
$redirect = Settings::get_settings_page_link();
?>
<div class="wp2fa-setup-footer">
<?php if ( 'welcome' !== self::$current_step && 'finish' !== self::$current_step ) { // Don't show the link on the first & last step. ?>
<?php if ( ! User_Helper::get_user_enforced_instantly( $user ) ) { ?>
<a class="close-wizard-link" href="<?php echo \esc_url( $redirect ); ?>"><?php \esc_html_e( 'Close Wizard', 'wp-2fa' ); ?></a>
<?php
}
}
?>
</div>
</div>
</body>
</html>
<?php
// phpcs:ignore
echo Generate_Modal::generate_modal(
'notify-admin-settings-page',
'',
__( 'If you cancel this wizard, the default plugin settings will be applied. You can always configure the plugin settings and two-factor authentication policies at a later stage from the ', 'wp-2fa' ) . ' <b>' . __( 'WP 2FA', 'wp-2fa' ) . '</b>' . __( ' entry in your WordPress dashboard menu.', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
array(
'<a href="#" id="close-settings" class="button button-primary wp-2fa-button-primary" data-redirect-url="' . \esc_url( $redirect ) . '">' . __( 'OK, close wizard', 'wp-2fa' ) . '</a>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'<a href="#" class="button button-secondary wp-2fa-button-secondary wp-2fa-button-secondary" data-close-2fa-modal>' . __( 'Continue with wizard', 'wp-2fa' ) . '</a>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
),
'',
'580px'
);
}
/**
* Setup Page Steps.
*
* @since 2.8.0
*/
private static function setup_page_steps() {
?>
<ul class="steps">
<?php
foreach ( self::$wizard_steps as $key => $step ) {
if ( 'welcome_wizard' === $step['wizard_type'] || is_array( $step['wizard_type'] ) && in_array( 'welcome_wizard', $step['wizard_type'], true ) ) {
if ( $key === self::$current_step ) {
?>
<li class="is-active"><?php echo \esc_html( $step['name'] ); ?></li>
<?php
} else {
?>
<li><?php echo \esc_html( $step['name'] ); ?></li>
<?php
}
}
}
?>
</ul>
<?php
}
/**
* Get Next Step URL.
*
* @return string
*
* @since 2.8.0
*/
private static function get_next_step() {
// Get current step.
$current_step = self::$current_step;
// Array of step keys.
$keys = array_keys( self::$wizard_steps );
if ( end( $keys ) === $current_step ) { // If last step is active then return WP Admin URL.
return admin_url();
}
// Search for step index in step keys.
$step_index = array_search( $current_step, $keys, true );
if ( false === $step_index ) { // If index is not found then return empty string.
return '';
}
// Return next step.
return add_query_arg( 'current-step', $keys[ $step_index + 1 ] );
}
/**
* Setup Page Content.
*
* @since 2.8.0
*/
private static function setup_page_content() {
?>
<div class="wp2fa-setup-content">
<?php
if ( ! empty( self::$wizard_steps[ self::$current_step ]['content'] ) ) {
call_user_func( self::$wizard_steps[ self::$current_step ]['content'] );
}
?>
</div>
<?php
}
/**
* Step View: `Welcome`
*
* @since 2.8.0
*/
private static function wp_2fa_step_welcome() {
Wizard_Steps::welcome_step( self::get_next_step() );
}
/**
* Step View: `Finish`
*
* @since 2.8.0
*/
private static function wp_2fa_step_finish() {
User_Helper::remove_user_needs_to_reconfigure_2fa( User_Helper::get_user_object() );
Wizard_Steps::congratulations_step( true );
}
/**
* Step Save: `Finish`
*
* @since 2.8.0
*/
private static function wp_2fa_step_finish_save() {
// Verify nonce.
\check_admin_referer( 'wp2fa-step-finish' );
\wp_safe_redirect( \esc_url_raw( self::get_next_step() ) );
exit();
}
/**
* Step View: `Choose Methods`
*
* @since 2.8.0
*/
private static function wp_2fa_step_global_2fa_methods() {
?>
<form method="post" class="wp2fa-setup-form wp2fa-form-styles wp2fa-first-time-wizard" autocomplete="off">
<?php wp_nonce_field( 'wp2fa-step-choose-method' ); ?>
<div class="step-setting-wrapper active" data-step-title="<?php \esc_html_e( '2FA methods', 'wp-2fa' ); ?>">
<?php First_Time_Wizard_Steps::select_method( true ); ?>
<div class="wp2fa-setup-actions">
<a class="button button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'Continue Setup', 'wp-2fa' ); ?>"><?php \esc_html_e( 'Continue Setup', 'wp-2fa' ); ?></a>
</div>
</div>
<div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Alternative methods', 'wp-2fa' ); ?>">
<?php First_Time_Wizard_Steps::backup_method( true ); ?>
<div class="wp2fa-setup-actions">
<a class="button button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'Continue Setup', 'wp-2fa' ); ?>"><?php \esc_html_e( 'Continue Setup', 'wp-2fa' ); ?></a>
</div>
</div>
<div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( '2FA policy', 'wp-2fa' ); ?>">
<?php First_Time_Wizard_Steps::enforcement_policy( true ); ?>
<div class="wp2fa-setup-actions">
<a class="button button-primary continue-wizard hidden" name="next_step_setting" value="<?php \esc_attr_e( 'Continue Setup', 'wp-2fa' ); ?>"><?php \esc_html_e( 'Continue Setup', 'wp-2fa' ); ?></a>
<button class="button button-primary save-wizard" type="submit" name="save_step" value="<?php \esc_attr_e( 'All done', 'wp-2fa' ); ?>"><?php \esc_html_e( 'All done', 'wp-2fa' ); ?></button>
</div>
</div>
<div class="step-setting-wrapper hidden" data-step-title="<?php \esc_html_e( 'Exclude users', 'wp-2fa' ); ?>">
<?php First_Time_Wizard_Steps::exclude_users( true ); ?>
<div class="wp2fa-setup-actions">
<a class="button button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'Continue Setup', 'wp-2fa' ); ?>"><?php \esc_html_e( 'Continue Setup', 'wp-2fa' ); ?></a>
</div>
</div>
<?php if ( WP_Helper::is_multisite() ) : ?>
<div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Exclude sites', 'wp-2fa' ); ?>">
<?php First_Time_Wizard_Steps::excluded_network_sites( true ); ?>
<div class="wp2fa-setup-actions">
<a class="button button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'Continue Setup', 'wp-2fa' ); ?>"><?php \esc_html_e( 'Continue Setup', 'wp-2fa' ); ?></a>
</div>
</div>
<?php endif; ?>
<div class="step-setting-wrapper hidden" data-step-title="<?php \esc_html_e( 'Grace period', 'wp-2fa' ); ?>">
<h3><?php \esc_html_e( 'How long should the grace period for your users be?', 'wp-2fa' ); ?></h3>
<p class="description"><?php \esc_html_e( 'When you configure the 2FA policies and require users to configure 2FA, they can either have a grace period to configure 2FA, or can be required to configure 2FA before the next time they login. Choose which method you\'d like to use:', 'wp-2fa' ); ?></p>
<?php First_Time_Wizard_Steps::grace_period( true ); ?>
<div class="wp2fa-setup-actions">
<button class="button button-primary save-wizard" type="submit" name="save_step" value="<?php \esc_attr_e( 'All done', 'wp-2fa' ); ?>"><?php \esc_html_e( 'All done', 'wp-2fa' ); ?></button>
</div>
</div>
</form>
<?php
}
/**
* Step Save: `Choose Method`
*
* @since 2.8.0
*/
private static function wp_2fa_step_global_2fa_methods_save() {
// Check nonce.
\check_admin_referer( 'wp2fa-step-choose-method' );
$input = ( isset( $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] ) && ! empty( $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] ) && \is_array( $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) ? $_POST[ WP_2FA_POLICY_SETTINGS_NAME ] : array();
$input = \map_deep( $input, 'wp_unslash' );
$input = \map_deep( $input, 'sanitize_text_field' );
if ( ! WP_Helper::is_multisite() ) {
\unregister_setting(
WP_2FA_POLICY_SETTINGS_NAME,
WP_2FA_POLICY_SETTINGS_NAME
);
}
$sanitized_settings = Settings_Page_Policies::validate_and_sanitize( $input, 'setup_wizard' );
WP2FA::update_plugin_settings( $sanitized_settings );
\wp_safe_redirect( \esc_url_raw( self::get_next_step() ) );
exit();
}
/**
* Send email with fresh code, or to setup email 2fa.
*
* @param int $user_id - User id we want to send the message to.
* @param string $nominated_email_address - The user custom address to use (name of the meta key to check for).
* @param bool $is_reset_protection - That call is for reset code.
*
* @return bool
*
* @since 2.8.0
*/
public static function send_authentication_setup_email( $user_id, $nominated_email_address = 'nominated_email_address', $is_reset_protection = false ) {
// If we have a nonce posted, check it.
if ( \wp_doing_ajax() && isset( $_POST['nonce'] ) ) {
$nonce_check = \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['nonce'] ) ), 'wp-2fa-send-setup-email' );
if ( ! $nonce_check ) {
\wp_send_json_error( new \WP_Error( 400, \esc_html__( 'Nonce checking failed', 'wp-2fa' ) ), 400 );
return false;
}
}
if ( isset( $_POST['user_id'] ) ) {
$user = get_userdata( intval( $_POST['user_id'] ) );
} else {
$user = get_userdata( $user_id );
}
// Grab email address is its provided.
if ( isset( $_POST['email_address'] ) ) {
$email = sanitize_email( \wp_unslash( $_POST['email_address'] ) );
} else {
$email = sanitize_email( $user->user_email );
}
if ( wp_doing_ajax() && isset( $_POST['nonce'] ) ) {
User_Helper::set_nominated_email_for_user( $email, $user );
}
$email_address = '';
if ( ! empty( $nominated_email_address ) ) {
if ( 'nominated_email_address' === $nominated_email_address ) {
$email_address = User_Helper::get_nominated_email_for_user( $user );
} elseif ( 'backup_email_address' === $nominated_email_address ) {
$email_address = User_Helper::get_backup_email_for_user( $user );
}
} else {
$email_address = $user->user_email;
}
// Generate a token and setup email.
$token = Authentication::generate_token( $user->ID );
if ( $is_reset_protection ) {
$subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'reset_password_code_email_subject' ), $user->ID ) );
$message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'reset_password_code_email_body' ), $user->ID, $token ) );
} elseif ( wp_doing_ajax() && isset( $_POST['nonce'] ) ) {
$subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'login_code_setup_email_subject' ), $user->ID ) );
$message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'login_code_setup_email_body' ), $user->ID, $token ) );
} else {
$subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'login_code_email_subject' ), $user->ID ) );
$message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'login_code_email_body' ), $user->ID, $token ) );
}
// If we have a nonce posted, check it.
if ( \wp_doing_ajax() && isset( $_POST['nonce'] ) ) {
$mail_sent = Settings_Page::send_email( $email_address, $subject, $message );
if ( ! $mail_sent ) {
\wp_send_json_error( new \WP_Error( 500, \esc_html__( 'Email sending failed', 'wp-2fa' ) ), 400 );
return false;
}
return $mail_sent;
}
return Settings_Page::send_email( $email_address, $subject, $message );
}
/**
* 3rd Party plugins
*
* @param array $wizard_steps - Array with the current wizard steps.
*
* @return array
*
* @since 2.8.0
*/
public static function wp_2fa_add_intro_step( $wizard_steps ) {
$new_wizard_steps = array(
'test' => array(
'name' => __( 'Welcome to WP 2FA', 'wp-2fa' ),
'content' => array( __CLASS__, 'introduction_step' ),
'save' => array( __CLASS__, 'introduction_step_save' ),
'wizard_type' => 'welcome_wizard',
),
);
// combine the two arrays.
$wizard_steps = $new_wizard_steps + $wizard_steps;
return $wizard_steps;
}
/**
* Shows introduction step of the wizard
*
* @return void
*
* @since 2.8.0
*/
private static function introduction_step() {
Wizard_Steps::introduction_step();
}
/**
* Step Save: `Addons`
*
* @since 2.8.0
*/
private static function introduction_step_save() {
// Check nonce.
check_admin_referer( 'wp2fa-step-addon' );
wp_safe_redirect( \esc_url_raw( self::get_next_step() ) );
exit();
}
}
}
includes/classes/class-wp2fa.php 0000644 00000164715 15075513060 0012662 0 ustar 00 <?php
/**
* Main plugin class.
*
* @package wp2fa
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA;
use WP2FA\Admin\User_Listing;
use WP2FA\Admin\User_Notices;
use WP2FA\Admin\FlyOut\FlyOut;
use WP2FA\Admin\Settings_Page;
use WP2FA\Utils\Request_Utils;
use WP2FA\Utils\Settings_Utils;
use WP2FA\Shortcodes\Shortcodes;
use WP2FA\Utils\Date_Time_Utils;
use WP2FA\Authenticator\Open_SSL;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Freemius\User_Licensing;
use WP2FA\Admin\Views\Re_Login_2FA;
use WP2FA\Admin\Controllers\Methods;
use WP2FA\Admin\Helpers\File_Writer;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Plugin_Updated_Notice;
use WP2FA\Admin\Helpers\Classes_Helper;
use WP2FA\Admin\Helpers\Methods_Helper;
use WP2FA\Admin\Views\Password_Reset_2FA;
use WP2FA\Admin\Views\Grace_Period_Notifications;
if ( ! class_exists( '\WP2FA\WP2FA' ) ) {
/**
* Main WP2FA Class.
*/
class WP2FA {
/**
* Holds the global plugin secret key
*
* @var string
*
* @since 2.0.0
*/
private static $secret_key = null;
/**
* Local static cache for plugins settings.
*
* @var array
*
* @since 2.0.0
*/
private static $plugin_settings = array();
/**
* Local static cache for plugins settings.
*
* @var array
*
* @since 2.8.0
*/
private static $default_settings = array();
/**
* Local static cache for email template settings.
*
* @var array
*/
protected static $wp_2fa_email_templates;
/**
* Array with all the plugin default settings.
*
* @return array
*
* @since 2.2.0
*/
public static function get_default_settings() {
if ( empty( self::$default_settings ) ) {
self::$default_settings = array(
'enforcement-policy' => 'do-not-enforce',
'excluded_users' => array(),
'excluded_roles' => array(),
'enforced_users' => array(),
'enforced_roles' => array(),
'grace-period' => 3,
'grace-period-denominator' => 'days',
'enable_destroy_session' => '',
'limit_access' => '',
'brute_force_disable' => '',
'2fa_settings_last_updated_by' => '',
'2fa_main_user' => '',
'grace-period-expiry-time' => '',
'plugin_version' => WP_2FA_VERSION,
'delete_data_upon_uninstall' => '',
'excluded_sites' => array(),
'included_sites' => array(),
'create-custom-user-page' => 'no',
'redirect-user-custom-page' => '',
'redirect-user-custom-page-global' => '',
'custom-user-page-url' => '',
'custom-user-page-id' => '',
'hide_remove_button' => '',
'separate-multisite-page-url' => '',
'grace-policy' => 'use-grace-period',
'superadmins-role-add' => 'no',
'superadmins-role-exclude' => 'no',
'default-text-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
'default-text-pw-reset-code-page' => '<p>' . __( 'You have been sent a one-time code via email. Please enter the code below and then click Get New Password to proceed with the password reset.', 'wp-2fa' ) . '</p><br><p><strong>' . __( 'Note: If you have not received the code please click the button Resend Code. If you still do not get the code after pressing the button, please contact the website\'s administrator.', 'wp-2fa' ) . '</strong></p>',
'default-2fa-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
'default-2fa-resetup-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
'custom-text-authy-code-page-intro' => __( 'If you are using the Authy app approve the OneTouch request to log in.', 'wp-2fa' ),
'custom-text-authy-code-page-awaiting' => __( 'Waiting for approval from application...', 'wp-2fa' ),
'custom-text-authy-code-page' => __( 'Manually enter the code from the mobile app.', 'wp-2fa' ),
'custom-text-twilio-code-page' => __( 'Enter the 2FA code you have received over SMS.', 'wp-2fa' ),
'custom-text-clickatell-code-page' => __( 'Enter the 2FA code you have received over SMS.', 'wp-2fa' ),
'custom-text-yubico-code-page' => __( 'Please insert the YubiKey in a USB port and touch / click the button on the YubiKey to generate the OTP required to log in.', 'wp-2fa' ),
'custom-text-app-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
'custom-text-email-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
'default-backup-code-page' => __( 'Enter a backup verification code.', 'wp-2fa' ),
'method_invalid_setting' => 'login_block',
'enable_wizard_styling' => 'enable_wizard_styling',
'show_help_text' => 'show_help_text',
'enable_wizard_logo' => '',
'enable_welcome' => '',
'welcome' => '',
'method_selection' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3>' . Methods::get_number_of_methods_text(),
'method_selection_single' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3><p>' . __( 'Only the below 2FA method is allowed on this website:', 'wp-2fa' ) . '</p>',
'method_help_authy_intro' => '<h3>' . __( 'Setting up Push notifications', 'wp-2fa' ) . '</h3><p>' . __( 'To enable push notifications enter the country and cellphone number in order to use it with this account.', 'wp-2fa' ) . '</p>',
'method_help_twilio_intro' => '<h3>' . __( 'Setting up 2FA over SMS', 'wp-2fa' ) . '</h3><p>' . __( 'When you use 2FA over SMS to log in to this website you will receive your one-time code via an SMS on your cellphone. Therefore please enter the cellphone number of where you would like to receive the SMS below.', 'wp-2fa' ) . '</p>',
'method_help_clickatell_intro' => '<h3>' . __( 'Setting up 2FA over SMS', 'wp-2fa' ) . '</h3><p>' . __( 'When you use 2FA over SMS to log in to this website you will receive your one-time code via an SMS on your cellphone. Therefore please enter the cellphone number of where you would like to receive the SMS below.', 'wp-2fa' ) . '</p>',
'method_help_oob_intro' => '<h3>' . __( 'Setting up Link over email 2FA', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the email address to where the out-of-band link should be sent:', 'wp-2fa' ) . '</p>',
'method_help_yubico_intro' => '<h3>' . __( 'Setting up 2FA with YubiKey', 'wp-2fa' ) . '</h3><p>' . __( '1 - Insert your YubiKey into the computer\'s / mobile\'s USB port', 'wp-2fa' ) . '</p><p>' . __( '2 - Touch / press the button on your YubiKey to generate the OTP code, which is automatically populated below', 'wp-2fa' ) . '</p>',
'method_verification_oob_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent to your email address to finalize the setup. Once the code is confirmed and 2FA is set up, you only have to verify a login by clicking on a link sent to you via email.', 'wp-2fa' ) . '</p>',
'method_verification_authy_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the code from your Authy application with name {authy_name}', 'wp-2fa' ) . '</p>',
'method_verification_twilio_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent via SMS to your phone to confirm your phone number.', 'wp-2fa' ) . '</p>',
'method_verification_clickatell_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent via SMS to your phone to confirm your phone number.', 'wp-2fa' ) . '</p>',
'method_verification_yubico_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Touch the YubiKey again to generate the OTP code to confirm the setup. Once the code is populated below, it should be automatically saved and verified. If that does not happen by any reason, once the secret key was pasted, click "Validate & save" button below to manually save and complete the configuration.', 'wp-2fa' ) . '</p>',
'backup_codes_intro_multi' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'It is recommended to configure a backup 2FA method in case you do not have access to the primary 2FA method to generate a code to log in. You can configure any of the below. You can always configure any or both from your user profile page later.', 'wp-2fa' ) . '</p>',
'backup_codes_intro' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Congratulations! You have enabled two-factor authentication for your user. You’ve just helped towards making this website more secure!', 'wp-2fa' ) . '</p>',
'backup_codes_intro_continue' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Congratulations! You have enabled two-factor authentication for your user. You’ve just helped towards making this website more secure!', 'wp-2fa' ) . '</p><p>' . __( 'You should now generate the list of backup method. Although this is optional, it is highly recommended to have a secondary 2FA method. This can be used as a backup should the primary 2FA method fail. This can happen if, for example, you forget your smartphone, the smartphone runs out of battery, or there are email deliverability problems.', 'wp-2fa' ) . '</p>',
'backup_codes_generate_intro' => '<h3>' . __( 'Generate list of backup codes', 'wp-2fa' ) . '</h3><p>' . __( 'It is recommended to generate and print some backup codes in case you lose access to your primary 2FA method.', 'wp-2fa' ) . '</p>',
'backup_codes_generated' => '<h3>' . __( 'Backup codes generated', 'wp-2fa' ) . '</h3><p>' . __( 'Here are your backup codes:', 'wp-2fa' ) . '</p>',
'no_further_action' => '<h3>' . __( 'Congratulations! You are all set.', 'wp-2fa' ),
'2fa_required_intro' => '<h3>' . __( 'You are required to configure 2FA.', 'wp-2fa' ) . '</h3><p>' . __( 'In order to keep this site - and your details secure, this website’s administrator requires you to enable 2FA authentication to continue.', 'wp-2fa' ) . '</p><p>' . __( 'Two factor authentication ensures only you have access to your account by creating an added layer of security when logging in -', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank" rel="noopener">' . __( 'Learn more', 'wp-2fa' ) . '</a></p>',
'authy_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification method', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the push notifications configuration.', 'wp-2fa' ) . '</p>',
'authy_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'twilio_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method (Twilio)', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the mobile phone number where the one-time code should be sent.', 'wp-2fa' ) . '</p>',
'clickatell_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method (Clickatell)', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where code should be send:', 'wp-2fa' ) . '</p>',
'yubico_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} 2FA over YubiKey', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the YubiKey associated with your user.', 'wp-2fa' ) . '</p>',
'twilio_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA over SMS service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'clickatell_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA over SMS service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'yubico_reconfigure_intro_unavailable' => '<h3>' . __( ' {reconfigure_or_configure_capitalized} 2FA over YubiKey', 'wp-2fa' ) . '</h3><p>' . __( 'The Yubico service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
'oob_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} link over email method', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the email address where the link should be sent.', 'wp-2fa' ) . '</p>',
'custom_css' => '',
'login_custom_css' => '',
'logo-code-page' => '',
'disable_login_css' => '',
'login-to-view-area' => '<p>' . __( 'You must be logged in to view this page. {login_url}', 'wp-2fa' ) . '</p>',
'backup_email_intro' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Well done on configuring 2FA, your login has just got more secure. To make sure you never get locked out you are required to confirm your email address and use email as an alternative and backup 2FA method in case your primary method is unavailable. Please confirm your email address below', 'wp-2fa' ) . '</p>',
'user-profile-form-preamble-title' => __( 'Two-factor authentication settings', 'wp-2fa' ),
'user-profile-form-preamble-desc' => __( 'Add two-factor authentication to strengthen the security of your user account.', 'wp-2fa' ),
'use_custom_2fa_message' => 'use-defaults',
);
/**
* Gives the ability to filter the default settings array of the plugin
*
* @param array $settings - The array with all the default settings.
*
* @since 2.0.0
*/
self::$default_settings = \apply_filters( WP_2FA_PREFIX . 'default_settings', self::$default_settings );
}
return self::$default_settings;
}
/**
* Inits the plugin related classes and settings
*
* @return void
*
* @since 2.6.0
*/
public static function init() {
Methods_Helper::init();
self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() );
self::$plugin_settings[ WP_2FA_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_SETTINGS_NAME, array() );
self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() );
self::$wp_2fa_email_templates = Settings_Utils::get_option( WP_2FA_EMAIL_SETTINGS_NAME );
/** We need to exclude all the possible ways, that logic to be executed by some WP request which could come from cron job or AJAX call, which will break the wizard (by storing the settings for the plugin) before it is completed by the user. We also have to check if the user is still processing first time wizard ($_GET parameter), and if the wizard has been finished already (wp_2fa_wizard_not_finished) */
if ( Settings_Utils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) {
if ( ! Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) {
self::update_plugin_settings( self::get_default_settings() );
}
// Set a flag so we know we have default values present, not custom.
Settings_Utils::update_option( 'default_settings_applied', true );
Settings_Utils::delete_option( 'wizard_not_finished' );
}
WP_Helper::init();
// Bootstrap.
Core\setup();
if ( is_admin() ) {
User_Listing::init();
// Hide all unrelated to the plugin notices on the plugin admin pages.
\add_action( 'admin_print_scripts', array( '\WP2FA\Admin\Helpers\WP_Helper', 'hide_unrelated_notices' ) );
// FlyOut::init();
}
Grace_Period_Notifications::init();
Password_Reset_2FA::init();
Re_Login_2FA::init();
Shortcodes::init();
User_Notices::init();
Plugin_Updated_Notice::init();
self::add_actions();
// Inits all the additional free app extensions.
$free_extensions = Classes_Helper::get_classes_by_namespace( 'WP2FA\\App\\' );
foreach ( $free_extensions as $extension ) {
if ( method_exists( $extension, 'init' ) ) {
call_user_func_array( array( $extension, 'init' ), array() );
}
}
}
/**
* Inits all the plugin hooks
*
* @return void
*
* @since 2.6.0
*/
public static function add_actions() {
// Plugin redirect on activation, only if we have no settings currently saved.
if ( ( ! isset( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) || empty( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) && Settings_Utils::get_option( 'redirect_on_activate', false ) ) {
\add_action( 'admin_init', array( __CLASS__, 'setup_redirect' ), 10 );
} elseif ( ! \is_array( Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) ) {
Settings_Utils::delete_option( WP_2FA_POLICY_SETTINGS_NAME );
self::update_plugin_settings( self::get_default_settings() );
}
// SettingsPage.
if ( WP_Helper::is_multisite() ) {
\add_action( 'network_admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu_multisite' ) );
\add_action( 'network_admin_edit_update_wp2fa_network_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_options' ) );
\add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_email_options' ) );
\add_action( 'network_admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_network_admin_notice' ) );
\add_action( 'network_admin_notices', array( __CLASS__, 'wp_not_writable' ) );
} else {
\add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu' ) );
\add_action( 'admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_admin_notice' ) );
\add_action( 'admin_notices', array( __CLASS__, 'wp_not_writable' ) );
}
\add_action( 'wp_ajax_wp2fa_dismiss_notice_mail_domain', array( '\WP2FA\Admin\Settings_Page', 'dismiss_notice_mail_domain' ) );
\add_action( 'wp_ajax_nopriv_set_salt_key', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'set_salt_key' ) );
\add_action( 'wp_ajax_set_salt_key', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'set_salt_key' ) );
\add_action( 'wp_ajax_wp_2fa_get_all_users', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_all_users' ) );
\add_action( 'wp_ajax_wp_2fa_get_all_roles', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_ajax_user_roles' ) );
\add_action( 'wp_ajax_wp_2fa_get_all_network_sites', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_all_network_sites' ) );
\add_action( 'wp_ajax_unlock_account', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'unlock_account' ), 10, 1 );
\add_action( 'admin_action_unlock_account', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'unlock_account' ), 10, 1 );
\add_action( 'admin_action_remove_user_2fa', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'remove_user_2fa' ), 10, 1 );
\add_action( 'wp_ajax_remove_user_2fa', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'remove_user_2fa' ), 10, 1 );
\add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'hide_settings' ), 999 );
\add_action( 'plugin_action_links_' . WP_2FA_BASE, array( '\WP2FA\Admin\Settings_Page', 'add_plugin_action_links' ) );
\add_filter( 'display_post_states', array( '\WP2FA\Admin\Settings_Page', 'add_display_post_states' ), 10, 2 );
\add_action( 'wp_ajax_send_authentication_setup_email', array( '\WP2FA\Admin\Setup_Wizard', 'send_authentication_setup_email' ) );
\add_action( 'wp_ajax_send_backup_codes_email', array( '\WP2FA\Methods\Backup_Codes', 'send_backup_codes_email' ) );
\add_action( 'wp_ajax_regenerate_authentication_key', array( '\WP2FA\Methods\TOTP', 'regenerate_authentication_key' ) );
// User_Notices.
\add_action( 'wp_ajax_dismiss_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
\add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
\add_action( 'wp_logout', array( '\WP2FA\Admin\User_Notices', 'reset_nag' ), 10, 1 );
// User_Profile.
global $pagenow;
if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
\add_action( 'show_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
\add_action( 'edit_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
if ( WP_Helper::is_multisite() ) {
\add_action( 'personal_options_update', array( '\WP2FA\Admin\User_Profile', 'save_user_2fa_options' ) );
}
}
\add_filter( 'user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
if ( WP_Helper::is_multisite() ) {
\add_filter( 'ms_user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
}
\add_action( 'wp_ajax_validate_authcode_via_ajax', array( '\WP2FA\Admin\User_Profile', 'validate_authcode_via_ajax' ) );
\add_action( 'wp_ajax_wp2fa_test_email', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'handle_send_test_email_ajax' ) );
// Login.
\add_action( 'wp_login', array( '\WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
\add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
\add_action( 'login_form_validate_2fa', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
\add_action( 'login_form_backup_2fa', array( '\WP2FA\Authenticator\Login', 'backup_2fa' ) );
\add_action( 'login_enqueue_scripts', array( '\WP2FA\Authenticator\Login', 'dequeue_style' ), PHP_INT_MAX );
// Reset password.
\add_action( 'lostpassword_post', array( '\WP2FA\Authenticator\Reset_Password', 'lostpassword_post' ), 20, 2 );
\add_action( 'login_form_lostpassword', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ), 20 );
// \add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ) );.
/**
* Keep track of all the user sessions for which we need to invalidate the
* authentication cookies set during the initial password check.
*/
\add_action( 'set_auth_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
\add_action( 'set_logged_in_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
// Run only after the core wp_authenticate_username_password() check.
\add_filter( 'authenticate', array( '\WP2FA\Authenticator\Login', 'filter_authenticate' ), 50 );
\add_filter( 'wp_authenticate_user', array( '\WP2FA\Authenticator\Login', 'run_authentication_check' ), 10, 2 );
// User Register.
\add_action( 'set_user_role', array( '\WP2FA\Admin\User_Registered', 'check_user_upon_role_change' ), 10, 3 );
// Block users from admin if needed.
$user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
\add_action( $user_block_hook, array( __CLASS__, 'block_unconfigured_users_from_admin' ), 10 );
// Help & Contact Us.
\add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( '\WP2FA\Admin\Help_Contact_Us', 'add_extra_menu_item' ) );
// phpcs:disable
/* @free:start */
// phpcs:enable
// Premium Features.
\add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( 'WP2FA\Admin\Premium_Features', 'add_extra_menu_item' ) );
\add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( 'WP2FA\Admin\Premium_Features', 'add_settings_banner' ) );
\add_action( 'admin_footer', array( 'WP2FA\Admin\Premium_Features', 'pricing_new_tab_js' ) );
// phpcs:disable
/* @free:end */
// phpcs:enable
\add_action( 'admin_footer', array( '\WP2FA\Admin\User_Profile', 'dismiss_nag_notice' ) );
\add_action( WP_2FA_PREFIX . 'user_authenticated', array( __CLASS__, 'clear_user_after_login' ), 10, 1 );
\add_filter( 'mepr-auto-login', array( '\WP2FA\Authenticator\Login', 'mepr_login' ) );
}
/**
* Add actions specific to the wizard.
*
* @since 2.0.0
*/
public static function add_wizard_actions() {
if ( function_exists( 'wp_get_current_user' ) && \current_user_can( 'read' ) ) {
\add_action( 'admin_init', array( '\WP2FA\Admin\Setup_Wizard', 'setup_page' ), 10 );
}
}
/**
* Redirect user to 1st time setup.
*
* @since 2.0.0
*/
public static function setup_redirect() {
// Bail early before the redirect if the user can't manage options.
if ( ! \current_user_can( 'manage_options' ) ) {
return;
}
$registered_and_active = 'yes';
if ( function_exists( 'wp2fa_freemius' ) ) {
$registered_and_active = wp2fa_freemius()->is_registered() && wp2fa_freemius()->has_active_valid_license() ? 'yes' : 'no';
}
if ( Settings_Utils::get_option( 'redirect_on_activate', false ) && 'yes' === $registered_and_active ) {
// Delete redirect option.
Settings_Utils::delete_option( 'redirect_on_activate' );
Settings_Utils::update_option( 'wizard_not_finished', true );
$redirect = \add_query_arg(
array(
'page' => 'wp-2fa-setup',
'is_initial_setup' => 'true',
),
\network_admin_url( 'user-edit.php' )
);
\wp_safe_redirect( $redirect );
exit();
}
}
/**
* Util function to grab settings or apply defaults if no settings are saved into the db.
*
* @param string $setting_name Settings to grab value of.
* @param boolean $get_default_on_empty return default setting value if current one is empty.
* @param boolean $get_default_value return default value setting (ignore the stored ones).
* @param string $role - The name of the user role.
*
* @return mixed Settings value or default value.
*
* @since 2.0.0
*/
public static function get_wp2fa_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) {
$role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
return self::get_wp2fa_setting_generic( WP_2FA_POLICY_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value, $role );
}
/**
* Util function to grab settings or apply defaults if no settings are saved into the db.
*
* @param string $setting_name Settings to grab value of.
* @param boolean $get_default_on_empty return default setting value if current one is empty.
* @param boolean $get_default_value return default value setting (ignore the stored ones).
*
* @return mixed Settings value or default value.
*/
public static function get_wp2fa_general_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
return self::get_wp2fa_setting_generic( WP_2FA_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
}
/**
* Util function to grab white label settings or apply defaults if no settings are saved into the db.
*
* @param string $setting_name Settings to grab value of.
* @param boolean $get_default_on_empty return default setting value if current one is empty.
* @param boolean $get_default_value return default value setting (ignore the stored ones).
*
* @return string Settings value or default value.
*
* @since 2.0.0
*/
public static function get_wp2fa_white_label_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
return (string) self::get_wp2fa_setting_generic( WP_2FA_WHITE_LABEL_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
}
/**
* Generic method for extracting settings from the plugin
*
* @param string $wp_2fa_setting - The name of the settings type.
* @param string $setting_name - The name of the setting to extract.
* @param boolean $get_default_on_empty - Should we use default value on empty.
* @param boolean $get_default_value - Extract default value.
* @param string $role - The name of the user role.
*
* @return mixed
*
* @since 2.0.0
*/
private static function get_wp2fa_setting_generic( $wp_2fa_setting = WP_2FA_POLICY_SETTINGS_NAME, $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) {
$default_settings = self::get_default_settings();
$role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
if ( true === $get_default_value ) {
if ( isset( $default_settings[ $setting_name ] ) ) {
return $default_settings[ $setting_name ];
}
return false;
}
$apply_defaults = false;
$wp2fa_setting = self::$plugin_settings[ $wp_2fa_setting ];
// If we have no setting name, return them all.
if ( empty( $setting_name ) ) {
return $wp2fa_setting;
}
// First lets check if any options have been saved.
if ( empty( $wp2fa_setting ) || ! isset( $wp2fa_setting ) ) {
$apply_defaults = true;
}
if ( $apply_defaults ) {
return isset( $default_settings[ $setting_name ] ) ? $default_settings[ $setting_name ] : false;
} elseif ( ! isset( $wp2fa_setting[ $setting_name ] ) ) {
if ( true === $get_default_on_empty ) {
if ( isset( $default_settings[ $setting_name ] ) ) {
return $default_settings[ $setting_name ];
}
}
return false;
} elseif ( WP_2FA_POLICY_SETTINGS_NAME === $wp_2fa_setting ) {
/**
* Extensions could change the extracted value, based on custom / different / specific for role settings.
*
* @param mixed - Value of the setting.
* @param string - The name of the setting.
* @param string - The role name.
*
* @since 2.0.0
*/
return \apply_filters( WP_2FA_PREFIX . 'setting_generic', $wp2fa_setting[ $setting_name ], $setting_name, $role );
} else {
return $wp2fa_setting[ $setting_name ];
}
}
/**
* Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
*
* @param string $setting_name Settings to grab value of.
*
* @since 2.0.0
*/
public static function get_wp2fa_email_templates( $setting_name = '' ) {
// If we have no setting name, return what ever is saved.
if ( empty( $setting_name ) ) {
return self::$wp_2fa_email_templates;
}
// If we have a saved setting, return it.
if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
return self::$wp_2fa_email_templates[ $setting_name ];
}
// Create Login Code Message.
$login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
$login_code_body = '<p>' . sprintf(
// translators: The login code provided from the plugin.
\esc_html__( 'Enter %1$1s to log in.', 'wp-2fa' ),
'<strong>{login_code}</strong>'
);
$login_code_body .= '</p>';
$login_code_body .= '<p>' . \esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
$login_code_body .= '<p>' . \esc_html__( 'Email sent by', 'wp-2fa' );
$login_code_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
$login_code_body .= '</p>';
// Create Reset PW Code Message.
$reset_password_code_subject = __( '2FA code for password reset', 'wp-2fa' );
$reset_password_code_body = '<p>' . \esc_html__( 'Hello,', 'wp-2fa' ) . '</p>';
$reset_password_code_body = '<p>' . sprintf(
// translators: The login code provided from the plugin.
\esc_html__( 'Someone from the IP address %1$1s has requested a password reset for the user %2$2s on the website %3$3s. If this was you please use the below code to proceed with the password reset:', 'wp-2fa' ),
'{user_ip_address}',
'{user_login_name}',
'{site_url}'
);
$reset_password_code_body .= '<p><strong>{login_code}</strong></p>';
$reset_password_code_body .= '</p>';
$reset_password_code_body .= '<p>' . \esc_html__( 'If this was not you, ignore this email and contact your website administrator.', 'wp-2fa' ) . '</p>';
$login_code_setup_body = '<p>' . sprintf(
// translators: The login code provided from the plugin.
\esc_html__( 'Please enter this code to confirm 2FA setup: %1$1s', 'wp-2fa' ),
'<strong>{login_code}</strong>'
);
$login_code_setup_body .= '</p>';
$login_code_setup_body .= '<p>' . \esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
$login_code_setup_body .= '<p>' . \esc_html__( 'Email sent by', 'wp-2fa' );
$login_code_setup_body .= ' <a href="hhttps://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
$login_code_setup_body .= '</p>';
// Create User Locked Message.
$user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
$user_locked_body = '<p>' . \esc_html__( 'Hello.', 'wp-2fa' ) . '</p>';
$user_locked_body .= '<p>' . sprintf(
// translators: %1s - the name of the user
// translators: %2s - the name of the site.
\esc_html__( 'Since you have not enabled two-factor authentication for the user %1$1s on the website %2$2s within the grace period, your account has been locked.', 'wp-2fa' ),
'{user_login_name}',
'{site_name}'
);
$user_locked_body .= '</p>';
$user_locked_body .= '<p>' . \esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>';
$user_locked_body .= '<p>' . \esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
$user_locked_body .= '<p>' . \esc_html__( 'Email sent by', 'wp-2fa' );
$user_locked_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
$user_locked_body .= '</p>';
// Create User unlocked Message.
$user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
$user_unlocked_body = '';
$user_unlocked_body .= '<p>' . __( 'Hello,', 'wp-2fa' ) . '</p><p>' . \esc_html__( 'Your user', 'wp-2fa' ) . ' <strong>{user_login_name}</strong> ' . \esc_html__( 'on the website', 'wp-2fa' ) . ' {site_url} ' . __( 'has been unlocked. Please configure two-factor authentication within the grace period, otherwise your account will be locked again.', 'wp-2fa' ) . '</p>';
if ( ! empty( self::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
$user_unlocked_body .= '<p>' . __( 'You can configure 2FA from this page:', 'wp-2fa' ) . ' <a href="{2fa_settings_page_url}" target="_blank">{2fa_settings_page_url}.</a></p>';
}
$user_unlocked_body .= '<p>' . __( 'Thank you.', 'wp-2fa' ) . '</p><p>' . __( 'Email sent by', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>';
// Create User backup codes Message.
$user_backup_codes_subject = __( '2FA backup codes for user {user_login_name} on {site_name}', 'wp-2fa' );
$user_backup_codes_body = '';
$user_backup_codes_body .= '<p>' . __( 'Hello,', 'wp-2fa' ) . '</p><p>' . \esc_html__( 'Below please find the 2FA backup codes for your user', 'wp-2fa' ) . ' <strong>{user_login_name}</strong> ' . \esc_html__( 'on the website', 'wp-2fa' ) . ' <strong>{site_name}</strong>. ' . __( 'The website\'s URL is', 'wp-2fa' ) . ' {site_url} </p>';
$user_backup_codes_body .= '{backup_codes}';
$user_backup_codes_body .= '<p>' . __( 'Thank you for enabling 2FA on your account and helping us keeping the website secure.', 'wp-2fa' ) . '</p><p>' . __( 'Email sent by', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>';
// Array of defaults, now we have things setup above.
$default_settings = array(
'email_from_setting' => 'use-defaults',
'custom_from_email_address' => '',
'custom_from_display_name' => '',
'login_code_email_subject' => $login_code_subject,
'login_code_email_body' => $login_code_body,
'reset_password_code_email_subject' => $reset_password_code_subject,
'reset_password_code_email_body' => $reset_password_code_body,
'login_code_setup_email_subject' => $login_code_subject,
'login_code_setup_email_body' => $login_code_setup_body,
'user_account_locked_email_subject' => $user_locked_subject,
'user_account_locked_email_body' => $user_locked_body,
'user_account_unlocked_email_subject' => $user_unlocked_subject,
'user_account_unlocked_email_body' => $user_unlocked_body,
'user_backup_codes_email_subject' => $user_backup_codes_subject,
'user_backup_codes_email_body' => $user_backup_codes_body,
'send_account_locked_email' => 'enable_account_locked_email',
'send_account_unlocked_email' => 'enable_account_unlocked_email',
'send_login_code_email' => 'enable_send_login_code_email',
'send_reset_password_code_email' => 'enable_send_reset_password_code_email',
);
/**
* Allows 3rd party providers to their own settings for the mail templates.
*
* @param array $default_settings - Array with the default settings.
*
* @since 2.0.0
*/
$default_settings = \apply_filters( WP_2FA_PREFIX . 'mail_default_settings', $default_settings );
return $default_settings[ $setting_name ];
}
/**
* Util which we use to replace our {strings} with actual, useful stuff.
*
* @param string $input Text we are working on.
* @param int|string $user_id User id, if its needed.
* @param string $token Login code, if its needed..
* @param string $override_grace_period - Value to override grace period with.
*
* @return string The output, with all the {strings} swapped out.
*
* @since 2.0.0
*/
public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
$token = trim( (string) $token );
// Gather grace period.
$grace_period_string = '';
if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
$grace_period_string = $override_grace_period;
} else {
$grace_policy = self::get_wp2fa_setting( 'grace-policy' );
$grace_period_string = Date_Time_Utils::format_grace_period_expiration_string( $grace_policy );
}
// Setup user data.
if ( isset( $user_id ) && ! empty( $user_id ) ) {
$user = get_userdata( $user_id );
} else {
$user = wp_get_current_user();
}
// Setup token.
if ( isset( $token ) && ! empty( $token ) ) {
$login_code = $token;
} else {
$login_code = '';
}
$new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
if ( ! empty( $new_page_id ) ) {
$new_page_permalink = \get_permalink( $new_page_id );
} else {
$new_page_id = Settings::get_custom_settings_page_id( '', $user );
if ( ! empty( $new_page_id ) ) {
$new_page_permalink = \get_permalink( $new_page_id );
} else {
$new_page_permalink = '';
}
}
// These are the strings we are going to search for, as well as there respective replacements.
$replacements = array(
'{site_url}' => \esc_url( \get_bloginfo( 'url' ) ),
'{site_name}' => \sanitize_text_field( \get_bloginfo( 'name' ) ),
'{grace_period}' => \sanitize_text_field( $grace_period_string ),
'{user_login_name}' => \sanitize_text_field( $user->user_login ),
'{user_first_name}' => \sanitize_text_field( $user->user_firstname ),
'{user_last_name}' => \sanitize_text_field( $user->user_lastname ),
'{user_display_name}' => \sanitize_text_field( $user->display_name ),
'{login_code}' => $login_code,
'{2fa_settings_page_url}' => \esc_url( $new_page_permalink ),
'{user_ip_address}' => Request_Utils::get_ip(),
);
/**
* 3rd party plugins could change the mail strings, or provide their own.
*
* @param array $replacements - The array with all the currently supported strings.
*/
$replacements = \apply_filters(
WP_2FA_PREFIX . 'replacement_email_strings',
$replacements
);
$final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
return $final_output;
}
/**
* Util which contextualizes the wording 'reconfigure'/'configure' as needed.
*
* @param string $input - Text we are working on.
* @param int|string $user_id - User id, if its needed.
* @param string $method_to_check - Name of the method to check for.
*
* @return string The output, with all the {strings} swapped out.
*
* @since 2.5.0
*/
public static function contextual_reconfigure_text( $input = '', $user_id = '', $method_to_check = '' ) {
if ( empty( trim( (string) $input ) ) ) {
return $input;
}
$enabled_method = User_Helper::get_enabled_method_for_user( $user_id );
$text = ( $enabled_method === $method_to_check ) ? \esc_html__( 'Reconfigure', 'wp-2fa' ) : \esc_html__( 'Configure', 'wp-2fa' );
$replacements = array(
'{reconfigure_or_configure_capitalized}' => $text,
'{reconfigure_or_configure}' => strtolower( $text ),
);
/**
* 3rd party plugins could change this to their own.
*
* @param array $replacements - The array with all the currently supported strings.
*
* @since 2.5.0
*/
$replacements = \apply_filters(
WP_2FA_PREFIX . 'replacement_reconfigure_strings',
$replacements
);
return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
}
/**
* Util replace replace a placeholder with the actual remaining grace period for a user..
*
* @param string $input - Text we are working on.
* @param int $grace_expiry - Expiration time.
*
* @return string The output, with all the {strings} swapped out.
*
* @since 2.5.0
*/
public static function replace_remaining_grace_period( $input = '', $grace_expiry = -1 ) {
if ( empty( trim( (string) $input ) ) || empty( trim( (string) $grace_expiry ) ) ) {
return $input;
}
$replacements = array(
'{grace_period_remaining}' => \esc_attr( Date_Time_Utils::format_grace_period_expiration_string( null, $grace_expiry ) ),
);
return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
}
/**
* Util which we use to replace our {strings} with actual, useful stuff.
*
* @param string $input Text we are working on.
* @param WP_User $user The WP User.
*
* @return string The output, with all the {strings} swapped out.
*
* @since 2.0.0
*/
public static function replace_wizard_strings( $input = '', $user = false ) {
if ( ! $user ) {
return $input;
}
$available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
// These are the strings we are going to search for, as well as there respective replacements.
$replacements = array(
'{available_methods_count}' => count( $available_methods[ User_Helper::get_user_role( $user ) ] ),
);
/**
* 3rd party plugins could change the mail strings, or provide their own.
*
* @param array $replacements - The array with all the currently supported strings.
*/
$replacements = \apply_filters(
WP_2FA_PREFIX . 'replacement_wizard_strings',
$replacements
);
$final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
return $final_output;
}
/**
* If a user is trying to access anywhere other than the 2FA config area, this blocks them.
*
* @return void
*
* @since 2.0.0
*/
public static function block_unconfigured_users_from_admin() {
global $pagenow;
$user = User_Helper::get_user();
if ( 0 === $user->ID ) {
return;
}
$redirect = true;
if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
if ( Extensions_Loader::use_proxytron() ) {
$redirect = User_Licensing::enable_2fa_user_setting( true );
}
}
if ( $redirect ) {
$is_user_instantly_enforced = User_Helper::get_user_enforced_instantly();
$grace_period_expiry_time = (int) User_Helper::get_user_expiry_date();
$time_now = time();
if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User_Helper::is_excluded( $user->ID ) ) {
$has_cap = true;
if ( class_exists( 'WooCommerce', false ) ) {
// Lets check if the user has the required capabilities to view the 2FA settings page (or profile page in the Admin section - dashboard).
$has_cap = false;
$access_caps = array( 'edit_posts', 'manage_woocommerce', 'view_admin_dashboard' );
foreach ( $access_caps as $access_cap ) {
if ( \current_user_can( $access_cap ) ) {
$has_cap = true;
break;
}
}
}
/**
* We should only allow:
* - 2FA setup wizard in the administration
* - custom 2FA page if enabled and created
* - AJAX requests originating from these 2FA setup UIs
*/
if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) { // phpcs:ignore
return;
}
if ( is_admin() || is_network_admin() ) {
$allowed_admin_page = 'profile.php';
if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) ) { // phpcs:ignore
return;
}
}
if ( is_page() ) {
$custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
if ( ! empty( $custom_user_page_id ) && \get_the_ID() === (int) $custom_user_page_id ) {
return;
} else {
$custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
if ( ! empty( $custom_user_page_id ) && \get_the_ID() === (int) $custom_user_page_id ) {
return;
}
}
}
// force a redirect to the 2FA set-up page if it exists.
$custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
if ( ! empty( $custom_user_page_id ) ) {
\wp_redirect( Settings::get_custom_page_link( $user ) );
exit;
} else {
$custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
if ( ! empty( $custom_user_page_id ) && \get_the_ID() === (int) $custom_user_page_id ) {
\wp_redirect( \get_permalink( $custom_user_page_id ) );
exit;
}
}
// There is nowhere to redirect, so we have to fall back to the default which is the dashboard. If the user does not have the required capabilities to view the dashboard - lets stop the redirection.
if ( ! $has_cap ) {
// Is there WOO installed? If so, then lets try to extract the redirection rules from there.
if ( class_exists( 'WooCommerce', false ) ) {
// Lets check if there is a 2FA implemented within the WOOCommerce myaccount page.
$items = \wc_get_account_menu_items();
if ( isset( $items['wp-2fa'] ) ) {
if ( ! isset( $_GET['wp-2fa'] ) ) {
$url = \add_query_arg(
array(
'wp-2fa' => '',
),
\get_permalink( \get_option( 'woocommerce_myaccount_page_id' ) )
);
\wp_redirect( $url );
exit;
}
}
}
// Nothing suitable found - notify the admin and bail.
$transient_name = WP_2FA_PREFIX . '_notified_admin_mail_nowhere_to_redirect_' . $user->ID;
if ( false === \get_transient( $transient_name ) ) {
$subject = sprintf(
// translators: The username.
\esc_html__(
'User %1$s logged in without 2FA',
'wp-2fa'
),
$user->user_login,
);
$text = sprintf(
// translators: The username.
// translators: the site name.
\esc_html__(
'2FA is enforced on the user %1$s on the website %2$s. However, since the WP 2FA plugin has not been configured properly it cannot enforce the user to configure 2FA, so the user logged in without 2FA.',
'wp-2fa'
),
$user->user_login,
\get_bloginfo( 'name' )
);
$text .= '<p>' . sprintf(
// translators: the settings page.
// translators: the support e-mail.
\esc_html__(
'To enforce 2FA on users logging in from non default WordPress login pages please configure the %1$s. If you need assistance, please contact us at %2$s.',
'wp-2fa'
),
'<a href="' . \esc_url(
\add_query_arg(
array(
'page' => 'wp-2fa-settings',
'tab' => 'integrations',
),
\network_admin_url( 'admin.php' )
)
) . '">front-end 2FA page</a>',
'<a href="mailto:support@melapress.com">support@melapress.com</a>'
) . '</p>';
Settings_Page::send_email(
\get_option( 'admin_email' ),
$subject,
$text
);
\set_transient( $transient_name, 'sent', DAY_IN_SECONDS * 2 );
}
return;
}
// custom 2FA page is not set-up, force redirect to the wizard in administration.
\wp_redirect( Settings::get_setup_page_link() );
exit;
}
}
}
/**
* Returns currently stored settings
*
* @return array
*
* @since 2.0.0
*/
public static function get_policy_settings() {
/**
* Extensions could change the stored settings value, based on custom / different / specific for role settings.
*
* @param array - Value of the settings.
*
* @since 2.0.0
*/
$settings = \apply_filters( WP_2FA_PREFIX . 'policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] );
return $settings;
}
/**
* Checks the action parameter against given list of actions
*
* @return bool
*
* @since 2.0.0
*/
private static function action_check() {
if ( ! isset( $_REQUEST['action'] ) ) { //phpcs:ignore -- No nonce - that is not needed here
return false;
}
$actions_array = array(
'send_authentication_setup_email',
'validate_authcode_via_ajax',
'heartbeat',
'regenerate_authentication_key',
'send_backup_codes_email',
'register_user_twilio',
'register_user_clickatell',
);
/**
* Allows 3rd party providers to their own settings for the mail templates.
*
* @param array $actions_array - Array with the default settings.
*
* @since 2.0.0
*/
$actions_array = \apply_filters( WP_2FA_PREFIX . 'actions_check', $actions_array );
return in_array( $_REQUEST['action'], $actions_array, true );
}
/**
* Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings.
*
* @param array $settings - The settings values.
* @param bool $skip_option_save If true, the settings themselves are not saved. This is needed when saving settings from settings page as WordPress options API takes care of that.
* @param string $settings_name - The name of the settings to extract.
*
* @since 2.0.0
*/
public static function update_plugin_settings( $settings, $skip_option_save = false, $settings_name = WP_2FA_POLICY_SETTINGS_NAME ) {
// update local copy of settings.
self::$plugin_settings[ $settings_name ] = $settings;
if ( ! $skip_option_save ) {
// update the database option itself.
Settings_Utils::update_option( $settings_name, $settings );
}
if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) {
// Create a hash for comparison when we interact with a use.
$settings_hash = Settings_Utils::create_settings_hash( self::get_policy_settings() );
Settings_Utils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash );
}
}
/**
* Getter for the secret key of the plugin for the current instance
*
* Note: that is legacy code and will be removed.
*
* @return string
*
* @since 2.0.0
*/
public static function get_secret_key() {
if ( null === self::$secret_key ) {
if ( ! defined( File_Writer::SECRET_NAME ) ) {
self::check_for_key();
} else {
self::$secret_key = constant( File_Writer::SECRET_NAME );
}
}
return self::$secret_key;
}
/**
* Checks if the wp-config.php file is writable, show notice to the admin if it is not
*
* @return void
*
* @since 2.4.0
*/
public static function wp_not_writable() {
if ( ! \defined( 'WP2FA_SECRET_IS_IN_DB' ) || true !== WP2FA_SECRET_IS_IN_DB ) {
return;
}
if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
$whitelist_admin_pages = array(
'wp-2fa_page_wp-2fa-settings',
'wp-2fa_page_wp-2fa-settings-network',
'toplevel_page_wp-2fa-policies',
'toplevel_page_wp-2fa-policies-network',
'wp-2fa_page_wp-2fa-help-contact-us',
'wp-2fa_page_wp-2fa-help-contact-us-network',
'wp-2fa_page_wp-2fa-policies-account',
'wp-2fa_page_wp-2fa-policies-account-network',
'wp-2fa_page_wp-2fa-reports',
'wp-2fa_page_wp-2fa-reports-network',
);
$admin_page = \get_current_screen();
if ( in_array( $admin_page->base, $whitelist_admin_pages ) ) {
?>
<div class="notice notice-warning" id="config-update-notice">
<?php
$message = sprintf(
'<p>%1$s <a href="https://melapress.com/support/kb/wp-2fa-add-2fa-plugin-encryption-key-wp-config/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" noopener target="_blank">%2$s</a><br>%3$s</p>',
\esc_html__( 'For security reasons WP 2FA needs to store the private key in the wp-config.php file. However, it is unable to. This can happen because of restrictive permissions, or the file is not in the default location. To fix this you can:', 'wp-2fa' ) . '<br><br>' .
\esc_html__( 'Option A) allow the plugin to write to the wp-config.php file temporarily by changing the wp-config.php permissions to 755. Once ready, click the button to proceed.', 'wp-2fa' ) . '<br>' .
\esc_html__( 'Option B) Add the encryption key to the wp-config.php file yourself by ', 'wp-2fa' ),
\esc_html__( 'following these instructions.', 'wp-2fa' ) . '<br>',
\esc_html__(
'Once you complete any of the above, please click the button below.
',
'wp-2fa'
),
)
?>
<?php echo $message; // phpcs:ignore ?>
<p><button id="salt-update" type="button">
<span><?php \esc_html_e( 'Write key to file now / Check for the key in file', 'wp-2fa' ); ?></span>
</button></p>
</div>
<script>
jQuery(document).ready(function($) {
$(document).on('click', '#salt-update', function( event ) {
const ajaxURL = (typeof wp2faWizardData != "undefined") ? wp2faWizardData.ajaxURL : ajaxurl;
const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-set-salt-nonce' ) ); ?>';
jQuery.ajax({
url: ajaxURL,
data: {
action: 'set_salt_key',
_wpnonce: nonceValue
},
success: function (data) {
if (data.success) {
jQuery('#config-update-notice .notice-dismiss').click();
} else {
alert(data.data);
}
},
error: function (data) {
alert(data.responseJSON.data[0].message);
}
});
});
});
</script>
<?php
}
}
}
/**
* Remove the user meta related with the code has been sent to the user.
* That is so we can lower the security by giving the option not to resend codes, so eventual brute force could succeed.
* The setting name - brute_force_disable
*
* @return void
*
* @since 2.5.0
*/
public static function clear_user_after_login() {
User_Helper::remove_meta( WP_2FA_PREFIX . 'code_sent' );
}
/**
* Checks and sets the global wp2fa salt
*
* @return void
*
* @since 2.4.0
*/
private static function check_for_key() {
self::$secret_key = Settings_Utils::get_option( 'secret_key' );
if ( empty( self::$secret_key ) ) {
self::$secret_key = base64_encode( Open_SSL::secure_random() ); // phpcs:ignore
if ( ! File_Writer::save_secret_key( self::$secret_key ) ) {
Settings_Utils::update_option( 'secret_key', self::$secret_key );
}
}
}
}
}
includes/classes/class-email-template.php 0000644 00000005135 15075513060 0014531 0 ustar 00 <?php
/**
* Responsible for email templates generation.
*
* @package wp2fa
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
namespace WP2FA;
if ( ! class_exists( '\WP2FA\Email_Template' ) ) {
/**
* Plain old PHP object to hold data for an email template.
*
* @package WP2FA
*/
class Email_Template {
/**
* Template ID used for most settings form fields and setting keys.
*
* @var string
*/
private $id;
/**
* The title of the email
*
* @var string
*/
private $title;
/**
* Email template description
*
* @var string
*/
private $description;
/**
* ID used for identifying the subject and body of the email. Defaults to $id.
*
* @var string ID used for identifying the subject and body of the email. Defaults to $id.
*/
private $email_content_id;
/**
* True if the email can be turned on or off in the plugin settings.
*
* @var bool
*/
private $can_be_toggled = true;
/**
* Email_Template constructor.
*
* @param string $id - The template ID.
* @param string $title - The title.
* @param string $description - The description.
*/
public function __construct( string $id, string $title, string $description ) {
$this->id = $id;
$this->title = $title;
$this->description = $description;
$this->email_content_id = $id;
}
/**
* Can it be toggled
*
* @return bool
*/
public function can_be_toggled(): bool {
return $this->can_be_toggled;
}
/**
* Sets the toggled flag for the template
*
* @param bool $can_be_toggled - Can it be toggled.
*/
public function set_can_be_toggled( $can_be_toggled ) {
$this->can_be_toggled = $can_be_toggled;
}
/**
* Returns the template ID
*
* @return string
*/
public function get_id(): string {
return $this->id;
}
/**
* Returns the title
*
* @return string
*/
public function get_title(): string {
return $this->title;
}
/**
* Returns the description
*
* @return string
*/
public function get_description(): string {
return $this->description;
}
/**
* Returns the mail content
*
* @return string
*/
public function get_email_content_id(): string {
return $this->email_content_id;
}
/**
* Set content ID
*
* @param string $email_content_id - the ID of the content.
*/
public function set_email_content_id( string $email_content_id ) {
$this->email_content_id = $email_content_id;
}
}
}
includes/classes/Authenticator/class-login.php 0000644 00000122541 15075513060 0015554 0 ustar 00 <?php
/**
* Responsible for WP2FA user's login forms.
*
* @package wp2fa
* @subpackage login
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Authenticator;
use WP2FA\WP2FA;
use WP2FA\Methods\TOTP;
use WP2FA\Methods\Email;
use WP2FA\Admin\Setup_Wizard;
use WP2FA\Methods\Backup_Codes;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Admin\Controllers\Methods;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Authenticator\Authentication;
use WP2FA\Methods\Wizards\TOTP_Wizard_Steps;
use WP2FA\Admin\Views\Grace_Period_Notifications;
use WP2FA\Admin\SettingsPages\Settings_Page_Policies;
/**
* Responsible for user login process.
*
* @since 2.0.0
*/
if ( ! class_exists( '\WP2FA\Authenticator\Login' ) ) {
/**
* Class for handling logins.
*/
class Login {
/**
* Keys used for backup codes
*
* @var string
*/
const USER_META_NONCE_KEY = 'wp_2fa_nonce';
const INPUT_NAME_RESEND_CODE = 'wp-2fa-email-code-resend';
/**
* Keep track of all the password-based authentication sessions that
* need to invalidated before the second factor authentication.
*
* @var array
*/
private static $password_auth_tokens = array();
/**
* Keep track of all the authentication cookies that need to be
* invalidated before the second factor authentication.
*
* @param string $cookie Cookie string.
*
* @return void
*/
public static function collect_auth_cookie_tokens( $cookie ) {
$parsed = wp_parse_auth_cookie( $cookie );
if ( ! empty( $parsed['token'] ) ) {
self::$password_auth_tokens[] = $parsed['token'];
}
}
/**
* Leave the memberpress alone
*
* @return bool
*
* @since 2.6.0
*/
public static function mepr_login(): bool {
\remove_action( 'wp_login', array( '\WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
return true;
}
/**
* Handle the browser-based login.
*
* Note: All user meta data is in sync with the current version of plugin settings. This is taken care of in filter
* wp_authenticate_user.
*
* @since 0.1-dev
*
* @param string $user_login Username.
* @param \WP_User $user \WP_User object of the logged-in user.
*
* @SuppressWarnings(PHPMD.ExitExpression)
*/
public static function wp_login( $user_login, $user ) {
if ( class_exists( '\wpengine\sign_on_plugin\WPESignOnPlugin' ) && isset( $_REQUEST['nonce'] ) && isset( $_REQUEST['install_name'] ) ) {
$user_nonce = new \wpengine\sign_on_plugin\UserNonceHelper();
$nonce = \wp_unslash( $_REQUEST['nonce'] );
$install_name = \wp_unslash( $_REQUEST['install_name'] );
$nonce_data = $user_nonce->get_nonce_data( $user->ID );
// At this stage we are pretty sure that it is wp engine and everything is OK. $nonce_data must be empty because they are using user_meta and it is deleted - so there is no way to do a second validation, but that is enough.
if ( empty( $nonce_data ) ) {
return;
}
}
// Flywheel auto login part starts here.
if ( defined( 'FW_DIRECT_LOGIN_SHARED_KEY' ) && isset( $_REQUEST['payload'] ) && isset( $_REQUEST['nonce'] ) && function_exists( 'sodium_crypto_secretbox_open' ) ) {
$playload = base64_decode( \wp_unslash( $_REQUEST['payload'] ) );
$nonce = base64_decode( \wp_unslash( $_REQUEST['nonce'] ) );
$key = file_get_contents( FW_DIRECT_LOGIN_SHARED_KEY );
$playload = sodium_crypto_secretbox_open( $playload, $nonce, $key );
if ( false !== $playload ) {
return;
}
}
// Flywheel auto login end.
global $wp_current_filter;
if ( isset( $wp_current_filter ) && ! empty( $wp_current_filter ) && \is_array( $wp_current_filter ) ) {
foreach ( $wp_current_filter as $filter ) {
if ( 'wp_ajax_nopriv_mepr_stripe_confirm_payment' === $filter ) {
// That request comes from unprivileged user (maybe new), lets skip our checks in that case.
return;
}
}
}
$user_status = User_Helper::get_2fa_status( $user );
if ( User_Helper::USER_UNDETERMINED_STATUS === $user_status ) {
User_Helper::remove_global_settings_hash_for_user( $user->ID );
}
User_Helper::set_login_date_for_user( time(), $user );
WP2FA::clear_user_after_login();
/**
* User is not required to use the 2FA
*/
if ( 'no_required_not_enabled' === $user_status ) {
return;
}
$global_methods = Methods::get_available_2fa_methods();
$users_method = User_Helper::get_enabled_method_for_user( $user );
$users_method_removed = false;
if ( User_Helper::is_enforced( $user ) && ! empty( $users_method ) && empty( \array_intersect( array( $users_method ), $global_methods ) ) ) {
$users_method_removed = true;
}
// leave if the user has already got 2FA authentication configured.
if ( ! $users_method_removed && User_Helper::is_user_using_two_factor( $user->ID ) ) {
// phpcs:disable
// phpcs:enable
try {
Settings::is_provider_enabled_for_role( User_Helper::get_user_role(), User_Helper::get_enabled_method_for_user( $user ) );
self::clear_session_and_show_2fa_form( $user );
return;
} catch ( \Exception $e ) {
return;
}
}
// Method is no longer available, but the user is using it - bail.
if ( $users_method_removed && User_Helper::is_user_using_two_factor( $user->ID ) ) {
return;
}
// leave if 2FA is not enforced, but optional.
$enforcement_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
if ( 'do-not-enforce' === $enforcement_policy ) {
return;
}
// leave if the user is not required to have 2FA enabled due to and exclusion rule.
if ( User_Helper::is_excluded( $user->ID ) ) {
return;
}
// redirect to 2FA setup page if the 2FA configuration is enforced to happen instantly.
$is_user_instantly_enforced = User_Helper::get_user_enforced_instantly( $user );
if ( true === (bool) $is_user_instantly_enforced ) {
wp_safe_redirect(
self::get_2fa_setup_url( $user ) . ( ( isset( $_REQUEST['_wp_http_referer'] ) && ! empty( $_REQUEST['_wp_http_referer'] ) ) ? '?return=' . urlencode( \esc_url_raw( \wp_unslash( $_REQUEST['_wp_http_referer'] ) ) ) : '' ) // phpcs:ignore
);
exit();
}
// if there is some grace period configured, and it is not instant, we can let the users in (if they needed to
// be blocked, this would have already happened in wp_authenticate).
$grace_policy = WP2FA::get_wp2fa_setting( 'grace-policy' );
if ( 'use-grace-period' === $grace_policy ) {
if ( ! Grace_Period_Notifications::notify_using_dashboard( $user ) ) {
$global_methods = Methods::get_available_2fa_methods();
$users_method = User_Helper::get_enabled_method_for_user( $user );
$is_nag_dismissed = User_Helper::get_nag_status();
$is_nag_needed = User_Helper::is_enforced( User_Helper::get_user_object()->ID );
if ( ! $is_nag_dismissed && $is_nag_needed ) {
$login_nonce = self::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : admin_url(); //phpcs:ignore
self::show_2fa_form_grace_form( $user, $login_nonce['key'], $redirect_to );
} else {
return;
}
} else {
return;
}
}
$provider = User_Helper::get_enabled_method_for_user( $user );
if ( '' === trim( (string) $provider ) ) {
return;
}
self::clear_session_and_show_2fa_form( $user );
}
/**
* Generates the html form for the second step of the authentication process.
*
* @since 2.5.0
*
* @param \WP_User $user \WP_User object of the logged-in user.
* @param string $login_nonce A string nonce stored in usermeta.
* @param string $redirect_to The URL to which the user would like to be redirected.
* @param string $error_msg Optional. Login error message.
*/
public static function show_2fa_form_grace_form( $user, $login_nonce, $redirect_to, $error_msg = '' ) {
$interim_login = isset( $_REQUEST['interim-login'] ) ? filter_var( wp_unslash( $_REQUEST['interim-login'] ), FILTER_VALIDATE_BOOLEAN ) : false; //phpcs:ignore
$rememberme = intval( self::rememberme() );
$global_methods = Methods::get_available_2fa_methods();
$users_method = User_Helper::get_enabled_method_for_user( $user );
if ( ! function_exists( 'login_header' ) ) {
// We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
include_once WP_2FA_PATH . 'includes/functions/login-header.php';
}
login_header();
if ( ! empty( $error_msg ) ) {
echo '<div id="login_error"><strong>' . apply_filters( 'login_errors', \esc_html( $error_msg ) ) . '</strong><br /></div>';
}
?>
<form name="grace_2fa_form" id="lgraceform" action="<?php echo \esc_url( self::login_url( array( 'action' => 'grace_2fa' ), 'login_post' ) ); ?>" method="post" autocomplete="off">
<input type="hidden" name="wp-auth-id" id="wp-auth-id" value="<?php echo \esc_attr( $user->ID ); ?>" />
<input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="<?php echo \esc_attr( $login_nonce ); ?>" />
<?php if ( $interim_login ) : ?>
<input type="hidden" name="interim-login" value="1" />
<?php else : ?>
<input type="hidden" name="redirect_to" value="<?php echo \esc_attr( $redirect_to ); ?>" />
<?php endif; ?>
<input type="hidden" name="rememberme" id="rememberme" value="<?php echo \esc_attr( $rememberme ); ?>"/>
<?php
$class = 'wp-2fa-nag';
if ( User_Helper::get_user_needs_to_reconfigure_2fa( User_Helper::get_user_object() ) ) {
$message = WP2FA::get_wp2fa_white_label_setting( 'default-2fa-resetup-required-notice', true );
} else {
$message = WP2FA::get_wp2fa_white_label_setting( 'default-2fa-required-notice', true );
}
$grace_expiry = (int) User_Helper::get_user_expiry_date( User_Helper::get_user_object() );
$setup_url = Settings::get_setup_page_link();
echo '<div class="' . \esc_attr( $class ) . '">';
echo \wpautop( \wp_kses_post( WP2FA::replace_remaining_grace_period( $message, $grace_expiry ) ) );
echo '<p> </p><div> <a href="' . \esc_url( $setup_url ) . '" class="button button-primary">' . \esc_html__( 'Configure 2FA now', 'wp-2fa' ) . '</a>';
echo ' <a href="#" class="button button-secondary dismiss-user-configure-nag">' . \esc_html__( 'I\'ll do it later', 'wp-2fa' ) . '</a></div>';
echo '</div>';
/**
* Allows 3rd parties to render something at the end of the existing grace form.
*
* @param \WP_User $user - User for which the login form is shown.
* @param string $provider - The name of the provider.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'grace_html_before_end', $user );
?>
</form>
<?php
/** This action is documented in wp-login.php */
do_action( 'login_footer' );
?>
</div>
<div class="clear"></div>
<?php wp_print_scripts( 'jquery' ); ?>
<script>
jQuery( document ).on( 'click', '.dismiss-user-configure-nag', function(e) {
e.preventDefault();
const thisNotice = jQuery( this ).closest( '.notice' );
jQuery.ajax( {
url: '<?php echo admin_url( 'admin-ajax.php' ); ?>',
data: {
action: 'dismiss_nag'
},
complete: function() {
window.location.replace( jQuery( '[name="redirect_to"]' ).val() );
},
} );
} );
</script>
<style>
#login form p:empty + p {
margin-top: 15px;
}
</style>
</body>
</html>
<?php
exit();
}
/**
* Clears current user session and displays a "clone" of login screen with form to capture 2FA code.
*
* It also terminates current web request.
*
* @param \WP_User $user WordPress user object.
*
* @since 2.0.0
*
* @SuppressWarnings(PHPMD.ExitExpression)
*/
private static function clear_session_and_show_2fa_form( $user ) {
/**
* The filter can be user to skip the 2FA "login" form in some cases. For example if the user has set their
* device as trusted.
*
* @param bool $skip
* @param \WP_User $user
*
* @return bool
*/
$should_form_be_skipped = apply_filters( WP_2FA_PREFIX . 'skip_2fa_login_form', false, $user );
if ( $should_form_be_skipped ) {
return;
}
// Invalidate the current login session to prevent from being re-used.
self::destroy_current_session_for_user( $user );
// Also clear the cookies which are no longer valid.
wp_clear_auth_cookie();
self::show_two_factor_login( $user );
exit;
}
/**
* Retrieves the correct URL to the 2FA setup page. It handles configurable custom page as well as multisite.
*
* @param \WP_User $user \WP_User object of the logged-in user.
*
* @return string 2FA setup page URL.
*
* @since 2.0.0
* @since 2.5.0 $user parameter is added
*/
private static function get_2fa_setup_url( $user ) {
$page_slug = Settings::get_role_or_default_setting( 'custom-user-page-url', $user );
// Lets check for multisite first and if that is the case - lets search for that page on the user's default blog.
if ( WP_Helper::is_multisite() && false !== Settings::get_role_or_default_setting( 'separate-multisite-page-url', $user ) && ! empty( $page_slug ) ) {
$blog_id = User_Helper::get_user_default_blog( $user );
if ( 0 === $blog_id ) {
$new_page_permalink = '';
} else {
// Switch to the blog context.
\switch_to_blog( $blog_id );
$page_exists = Settings_Page_Policies::get_post_by_post_name( $page_slug, 'page' );
// Restore global context.
\restore_current_blog();
if ( false === $page_exists ) {
// Switch to the blog context.
switch_to_blog( $blog_id );
$result = Settings_Page_Policies::generate_custom_user_profile_page( $page_slug, User_Helper::get_user_role( $user ) );
// Restore global context.
restore_current_blog();
if ( $result && ! is_wp_error( $result ) ) {
$new_page_permalink = get_permalink( $result );
}
} else {
$new_page_permalink = get_permalink( $page_exists->ID );
}
}
} else {
$page_exists = Settings_Page_Policies::get_post_by_post_name( $page_slug, 'page' );
if ( $page_exists instanceof \WP_Post ) {
$new_page_permalink = get_permalink( $page_exists->ID );
}
}
if ( ! empty( $new_page_permalink ) ) {
return $new_page_permalink;
}
// If multisite - redirect the user properly in the admin.
if ( WP_Helper::is_multisite() ) {
return Settings::get_setup_page_link();
}
return admin_url( 'profile.php' );
}
/**
* Destroy the known password-based authentication sessions for the current user.
*
* Is there a better way of finding the current session token without
* having access to the authentication cookies which are just being set
* on the first password-based authentication request.
*
* @param \WP_User $user User object.
*
* @return void
*/
public static function destroy_current_session_for_user( $user ) {
$session_manager = \WP_Session_Tokens::get_instance( $user->ID );
foreach ( self::$password_auth_tokens as $auth_token ) {
$session_manager->destroy( $auth_token );
}
}
/**
* Prevent login through XML-RPC and REST API for users with at least one
* 2FA method enabled.
*
* @param \WP_User|\WP_Error $user Valid \WP_User only if the previous filters
* have verified and confirmed the
* authentication credentials.
*
* @return \WP_User|\WP_Error
*/
public static function filter_authenticate( $user ) {
if ( $user instanceof \WP_User && self::is_api_request() && User_Helper::is_user_using_two_factor( $user->ID ) && ! self::is_user_api_login_enabled( $user->ID ) ) {
return new \WP_Error(
'invalid_application_credentials',
\esc_html__( 'Error: API login for user disabled.', 'wp-2fa' )
);
}
return $user;
}
/**
* Checks if the user should be locked and return WordPress error if that's the case. It doesn't check the account
* if it receives an error object as an input.
*
* @param \WP_User|\WP_Error $user User data.
* @param string $password Password.
*
* @return \WP_User|\WP_Error
*/
public static function run_authentication_check( $user, $password ) {
// we don't need to do anything if we already received an error.
if ( is_a( $user, '\WP_Error' ) ) {
return $user;
}
if ( User_Helper::is_user_locked( $user->ID ) && ! User_Helper::is_excluded( $user->ID ) ) {
return self::get_user_locked_error();
}
return $user;
}
/**
* Generates an error object representing locked user account.
*
* @return \WP_Error User account locked error.
* @since 2.0.0
*/
public static function get_user_locked_error() {
return new \WP_Error(
'account_locked',
\esc_html__( 'Your user account has been locked because you have not configured 2FA within the grace period. Please contact the website administrator to unlock your user and you can configure 2FA.', 'wp-2fa' )
);
}
/**
* If the current user can login via API requests such as XML-RPC and REST.
*
* @param integer $user_id User ID.
*
* @return boolean
*/
public static function is_user_api_login_enabled( $user_id ) {
return (bool) apply_filters( 'two_factor_user_api_login_enable', false, $user_id );
}
/**
* Is the current request an XML-RPC or REST request.
*
* @return boolean
*/
public static function is_api_request() {
if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
return true;
}
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
return true;
}
return false;
}
/**
* Display the login form.
*
* @since 0.1-dev
*
* @param \WP_User $user \WP_User object of the logged-in user.
*/
public static function show_two_factor_login( $user ) {
if ( ! $user ) {
$user = wp_get_current_user();
}
$login_nonce = self::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : admin_url(); //phpcs:ignore
if ( self::is_woocommerce_activated() ) {
$redirect_to = isset( $_REQUEST['redirect'] ) ? \esc_url_raw( wp_unslash( $_REQUEST['redirect'] ) ) : admin_url();
}
self::login_html( $user, $login_nonce['key'], $redirect_to );
}
/**
* Checks if woocommerce is enabled.
*
* @return boolean
*
* @since 2.2.2
*/
public static function is_woocommerce_activated(): bool {
if ( class_exists( 'woocommerce' ) ) {
return true;
} else {
return false;
}
}
/**
* Display the Backup code 2fa screen.
*
* @since 0.1-dev
*
* @SuppressWarnings(PHPMD.ExitExpression)
*/
public static function backup_2fa() {
if ( ! isset( $_GET['wp-auth-id'], $_GET['wp-auth-nonce'], $_GET['provider'] ) ) { //phpcs:ignore
return;
}
// Filter $_GET array for security.
$get_array = filter_input_array( INPUT_GET );
$auth_id = (int) $get_array['wp-auth-id'];
$user = \get_userdata( $auth_id );
if ( ! $user ) {
return;
}
$nonce = \sanitize_text_field( $get_array['wp-auth-nonce'] );
if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
wp_safe_redirect( get_bloginfo( 'url' ) );
exit;
}
if ( ! isset( $get_array['provider'] ) ) {
\wp_die( \esc_html__( 'Cheatin’ uh?', 'wp-2fa' ), 403 );
} else {
$provider = \sanitize_textarea_field( \wp_unslash( $_GET['provider'] ) ); //phpcs:ignore
}
\delete_transient( 'wp_2fa_code_login_' . $user->ID );
self::login_html( $user, $nonce, \esc_url_raw( \wp_unslash( $get_array['redirect_to'] ) ), '', $provider );
exit;
}
/**
* Generates the html form for the second step of the authentication process.
*
* @since 0.1-dev
*
* @param \WP_User $user \WP_User object of the logged-in user.
* @param string $login_nonce A string nonce stored in usermeta.
* @param string $redirect_to The URL to which the user would like to be redirected.
* @param string $error_msg Optional. Login error message.
* @param string|object $provider An override to the provider.
*/
public static function login_html( $user, $login_nonce, $redirect_to, $error_msg = '', $provider = null ) {
if ( ! $provider || ( Backup_Codes::METHOD_NAME === $provider && ! Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) ) ) {
$provider = User_Helper::get_enabled_method_for_user( $user );
}
$codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
$interim_login = isset( $_REQUEST['interim-login'] ) ? filter_var( wp_unslash( $_REQUEST['interim-login'] ), FILTER_VALIDATE_BOOLEAN ) : false; //phpcs:ignore
$rememberme = intval( self::rememberme() );
if ( ! function_exists( 'login_header' ) ) {
// We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
include_once WP_2FA_PATH . 'includes/functions/login-header.php';
}
login_header();
if ( ! empty( $error_msg ) ) {
echo '<div id="login_error"><strong>' . apply_filters( 'login_errors', \esc_html( $error_msg ) ) . '</strong><br /></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
<form name="validate_2fa_form" id="loginform" action="<?php echo \esc_url( self::login_url( array( 'action' => 'validate_2fa' ), 'login_post' ) ); ?>" method="post" autocomplete="off">
<input type="hidden" name="provider" id="provider" value="<?php echo \esc_attr( $provider ); ?>" />
<input type="hidden" name="wp-auth-id" id="wp-auth-id" value="<?php echo \esc_attr( $user->ID ); ?>" />
<input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="<?php echo \esc_attr( $login_nonce ); ?>" />
<?php if ( $interim_login ) : ?>
<input type="hidden" name="interim-login" value="1" />
<?php else : ?>
<input type="hidden" name="redirect_to" value="<?php echo \esc_attr( $redirect_to ); ?>" />
<?php endif; ?>
<input type="hidden" name="rememberme" id="rememberme" value="<?php echo \esc_attr( $rememberme ); ?>"/>
<?php
// Check to see what provider is set and give the relevant authentication page.
if ( TOTP::METHOD_NAME === $provider ) {
TOTP_Wizard_Steps::totp_authentication_page( $user );
} elseif ( Email::METHOD_NAME === $provider ) {
self::email_authentication_page( $user );
} elseif ( Backup_Codes::METHOD_NAME === $provider ) {
self::backup_codes_authentication_page( $user );
} else {
/**
* Allows 3rd parties to render their own 2FA "login" form.
*
* @param \WP_User $user - User for which the login form is shown.
* @param string $provider - The name of the provider.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'login_form', $user, $provider );
}
/**
* Gives the ability to remove the submit button from the plugin forms
*
* @param bool - Default at this point is true - no method is selected.
* @param array $input - The input array with all the data.
*
* @since 2.0.0
*/
$submit_button_disabled = apply_filters( WP_2FA_PREFIX . 'login_disable_submit_button', false, $user, $provider );
if ( ! $submit_button_disabled ) {
/**
* Allows 3rd parties to render something before the login button on the 2FA "login" form.
*
* @param \WP_User $user - User for which the login form is shown.
* @param string $provider - The name of the provider.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'login_before_submit_button', $user, $provider );
?>
<p>
<?php
if ( function_exists( 'submit_button' ) ) {
/**
* Using that filter, the default text of the login button could be changed
*
* @param callback - Callback function which is responsible for text manipulation.
*
* @since 2.0.0
*/
$button_text = apply_filters( WP_2FA_PREFIX . 'login_button_text', \esc_html__( 'Log In', 'wp-2fa' ) );
submit_button( $button_text );
?>
<script type="text/javascript">
setTimeout(function () {
var d
try {
d = document.getElementById('authcode')
d.value = ''
d.focus()
} catch (e) {}
}, 200)
</script>
<?php } ?>
</p>
<?php
if ( Email::METHOD_NAME === $provider ) {
?>
<p class="2fa-email-resend">
<input type="submit" class="button"
name="<?php echo \esc_attr( self::INPUT_NAME_RESEND_CODE ); ?>"
value="<?php \esc_attr_e( 'Resend Code', 'wp-2fa' ); ?>"/>
</p>
<?php
}
} // submit button not disabled
/**
* Allows 3rd parties to render something at the end of the existing login form.
*
* @param \WP_User $user - User for which the login form is shown.
* @param string $provider - The name of the provider.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'login_html_before_end', $user, $provider );
?>
</form>
<?php
if ( Backup_Codes::METHOD_NAME !== $provider && Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) && isset( $codes_remaining ) && $codes_remaining > 0 ) {
$login_url = self::login_url(
array(
'action' => 'backup_2fa',
'provider' => Backup_Codes::METHOD_NAME,
'wp-auth-id' => $user->ID,
'wp-auth-nonce' => $login_nonce,
'redirect_to' => $redirect_to,
'rememberme' => $rememberme,
)
);
?>
<div class="backup-methods-wrap">
<p class="backup-methods">
<a href="<?php echo \esc_url( $login_url ); ?>">
<?php \esc_html_e( 'Or, use a backup code.', 'wp-2fa' ); ?>
</a>
</p>
</div>
<?php
}
/**
* Allows 3rd parties to render something after the backup methods.
*
* @param \WP_User $user - User for which the login form is shown.
* @param string $provider - The name of the provider.
* @param string $login_nonce - The login nonce created.
* @param string $redirect_to - Where to redirect the user after successful login.
* @param bool $rememberme - Remember me status.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'login_html_after_backup_providers', $user, $provider, $login_nonce, $redirect_to, $rememberme );
?>
<p id="backtoblog">
<a href="<?php echo \esc_url( home_url( '/' ) ); ?>" title="<?php \esc_attr_e( 'Are you lost?', 'wp-2fa' ); ?>">
<?php
echo \esc_html(
sprintf(
// translators: %s: site name.
__( '← Back to %s', 'wp-2fa' ),
get_bloginfo( 'title', 'display' )
)
);
?>
</a>
</p>
</div>
<style>
/* @todo: migrate to an external stylesheet. */
.backup-methods-wrap {
margin-top: 16px;
padding: 0 24px;
}
.backup-methods-wrap a {
color: #50575e;
text-decoration: none;
}
ul.backup-methods {
display: none;
padding-left: 1.5em;
}
/* Prevent Jetpack from hiding our controls, see https://github.com/Automattic/jetpack/issues/3747 */
.jetpack-sso-form-display #loginform > p,
.jetpack-sso-form-display #loginform > div {
display: block;
}
</style>
<?php
/** This action is documented in wp-login.php */
do_action( 'login_footer' );
?>
<div class="clear"></div>
</body>
</html>
<?php
}
/**
* Generate the 2FA login form URL.
*
* @param array $params List of query argument pairs to add to the URL.
* @param string $scheme URL scheme context.
*
* @return string
*/
public static function login_url( $params = array(), $scheme = 'login' ) {
if ( ! is_array( $params ) ) {
$params = array();
}
$params = urlencode_deep( $params );
return add_query_arg( $params, site_url( 'wp-login.php', $scheme ) );
}
/**
* Create the login nonce.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
*
* @return array|bool
*/
public static function create_login_nonce( $user_id ) {
$login_nonce = array();
try {
$login_nonce['key'] = bin2hex( random_bytes( 32 ) );
} catch ( \Exception $ex ) {
$login_nonce['key'] = wp_hash( $user_id . mt_rand() . microtime(), 'nonce' ); //phpcs:ignore
}
$login_nonce['expiration'] = time() + HOUR_IN_SECONDS;
if ( ! update_user_meta( $user_id, self::USER_META_NONCE_KEY, $login_nonce ) ) {
return false;
}
return $login_nonce;
}
/**
* Delete the login nonce.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
*
* @return void
*/
public static function delete_login_nonce( $user_id ) {
User_Helper::remove_meta( self::USER_META_NONCE_KEY, $user_id );
}
/**
* Verify the login nonce.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
* @param string $nonce Login nonce.
* @return bool
*/
public static function verify_login_nonce( $user_id, $nonce ) {
$login_nonce = get_user_meta( $user_id, self::USER_META_NONCE_KEY, true );
if ( ! $login_nonce ) {
return false;
}
if ( $nonce !== $login_nonce['key'] || time() > $login_nonce['expiration'] ) {
self::delete_login_nonce( $user_id );
return false;
}
return true;
}
/**
* Login form validation.
*
* @since 0.1-dev
*
* @SuppressWarnings(PHPMD.ExitExpression)
*/
public static function login_form_validate_2fa() {
if ( ! isset( $_POST['wp-auth-id'], $_POST['wp-auth-nonce'] ) ) { // phpcs:ignore
return;
}
// If form data comes from 2 factor password reset - bounce.
if ( isset( $_POST['reset'] ) && 'reset-2fa' === $_POST['reset'] ) { // phpcs:ignore
return;
}
$auth_id = (int) $_POST['wp-auth-id']; // phpcs:ignore
$user = get_userdata( $auth_id );
if ( ! $user ) {
return;
}
$nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : ''; // phpcs:ignore
if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
wp_safe_redirect( get_bloginfo( 'url' ) );
exit;
}
if ( isset( $_POST['provider'] ) ) { // phpcs:ignore
$provider = sanitize_textarea_field( wp_unslash( $_POST['provider'] ) ); // phpcs:ignore
}
if ( ! Settings::is_provider_enabled_for_role( User_Helper::get_user_role( $user ), $provider ) ) {
wp_die( __( '<p> <strong>WP-2FA</strong>: Please contact the administrator for further assistance!</p>', 'wp-2fa' ) . \esc_html__( 'Invalid provider.', 'wp-2fa' ) ); // phpcs:ignore
}
// If this is an email login, or if the user failed validation previously, lets send the code to the user.
if ( Email::METHOD_NAME === $provider && true !== self::pre_process_email_authentication( $user ) ) {
$login_nonce = self::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
}
// Validate TOTP.
if ( TOTP::METHOD_NAME === $provider && true !== TOTP::validate_totp_authentication( $user ) ) {
do_action(
'wp_login_failed',
$user->user_login,
new \WP_Error(
'authentication_failed',
__( '<strong>Error</strong>: User can not be authenticated.', 'wp-2fa' )
)
);
$login_nonce = self::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
if ( Authentication::check_number_of_attempts( $user ) ) {
self::login_html( $user, $login_nonce['key'], \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ), \esc_html__( 'ERROR: Invalid verification code.', 'wp-2fa' ), $provider ); // phpcs:ignore
} else {
// Reached the maximum number of attempts - clear the attempts and redirect the user to the login page.
Authentication::clear_login_attempts( $user );
\wp_redirect( \wp_login_url() );
}
exit;
}
// Backup Codes.
if ( Backup_Codes::METHOD_NAME === $provider && true !== Backup_Codes::validate_backup_codes( $user ) ) {
do_action(
'wp_login_failed',
$user->user_login,
new \WP_Error(
'authentication_failed',
__( '<strong>Error</strong>: User can not be authenticated.', 'wp-2fa' )
)
);
$login_nonce = self::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
if ( Backup_Codes::check_number_of_attempts( $user ) ) {
self::login_html( $user, $login_nonce['key'], \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ), \esc_html__( 'ERROR: Invalid backup code.', 'wp-2fa' ), $provider ); // phpcs:ignore
} else {
Backup_Codes::clear_login_attempts( $user );
\wp_redirect( \wp_login_url() );
}
exit;
}
// Validate Email.
if ( Email::METHOD_NAME === $provider && true !== self::validate_email_authentication( $user ) ) {
do_action(
'wp_login_failed',
$user->user_login,
new \WP_Error(
'authentication_failed',
__( '<strong>Error</strong>: User can not be authenticated.', 'wp-2fa' )
)
);
$login_nonce = self::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
if ( isset( $_REQUEST['wp-2fa-email-code-resend'] ) ) { //phpcs:ignore
self::login_html( $user, $login_nonce['key'], \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ), \esc_html__( 'A new code has been sent.', 'wp-2fa' ), $provider ); // phpcs:ignore
} elseif ( Authentication::check_number_of_attempts( $user ) ) {
$msg = \esc_html__( 'ERROR: Invalid verification code.', 'wp-2fa' );
if ( empty( WP2FA::get_wp2fa_general_setting( 'brute_force_disable' ) ) ) {
$msg .= \esc_html__( ' For security reasons you have been sent a new code via email. Please use this new code to log in.', 'wp-2fa' );
}
self::login_html( $user, $login_nonce['key'], \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ), $msg, $provider ); // phpcs:ignore
} else {
Authentication::clear_login_attempts( $user );
\wp_redirect( \wp_login_url() );
}
exit;
}
/**
* Allows 3rd parties to validate their own 2FA "login" form.
*
* @param \WP_User $user - User for which the login form is shown.
* @param string $provider - The name of the provider.
*
* @since 2.0.0
*/
do_action( WP_2FA_PREFIX . 'validate_login_form', $user, $provider );
self::delete_login_nonce( $user->ID );
$rememberme = false;
$remember = ( isset( $_REQUEST['rememberme'] ) ) ? filter_var( $_REQUEST['rememberme'], FILTER_VALIDATE_BOOLEAN ) : ''; // phpcs:ignore
if ( ! empty( $remember ) ) {
$rememberme = true;
}
wp_set_auth_cookie( $user->ID, $rememberme );
/**
* Fires when the user is authenticated.
*
* @param \WP_User - the logged in user
*
* @since 2.0.0
*/
\do_action( WP_2FA_PREFIX . 'user_authenticated', $user );
// Must be global because that's how login_header() uses it.
global $interim_login;
$interim_login = ( isset( $_REQUEST['interim-login'] ) ) ? filter_var( $_REQUEST['interim-login'], FILTER_VALIDATE_BOOLEAN ) : false; // phpcs:ignore
if ( $interim_login ) {
$message = '<p class="message">' . __( 'You have logged in successfully.', 'wp-2fa' ) . '</p>';
$interim_login = 'success'; // phpcs:ignore
if ( ! function_exists( 'login_header' ) ) {
// We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
include_once WP_2FA_PATH . 'includes/functions/login-header.php';
}
login_header( '', $message );
?>
</div>
<?php
/** This action is documented in wp-login.php */
do_action( 'login_footer' );
?>
</body></html>
<?php
exit;
}
// Check if user has any roles/caps set - if they dont, we know its a "network" user.
if ( WP_Helper::is_multisite() && ! get_active_blog_for_user( $user->ID ) && empty( $user->caps ) && empty( $user->caps ) ) {
$redirect_to = user_admin_url();
} else {
$redirect_to = apply_filters( 'login_redirect', \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ), \esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ), $user ); // phpcs:ignore
}
Backup_Codes::clear_login_attempts( $user );
if ( ( empty( $redirect_to ) || 'wp-admin/' === $redirect_to || admin_url() === $redirect_to ) ) {
// If the user doesn't belong to a blog, send them to user admin. If the user can't edit posts, send them to their profile.
if ( WP_Helper::is_multisite() && ! get_active_blog_for_user( $user->ID ) && ! is_super_admin( $user->ID ) ) {
$redirect_to = user_admin_url();
} elseif ( WP_Helper::is_multisite() && ! $user->has_cap( 'read' ) ) {
$redirect_to = get_dashboard_url( $user->ID );
} elseif ( ! $user->has_cap( 'edit_posts' ) ) {
$redirect_to = $user->has_cap( 'read' ) ? admin_url( 'profile.php' ) : home_url();
}
$redirect_to = apply_filters( WP_2FA_PREFIX . 'post_login_orphan_user_redirect', $redirect_to, $user );
wp_redirect( $redirect_to );
exit;
}
$redirect_to = apply_filters( WP_2FA_PREFIX . 'post_login_user_redirect', $redirect_to, $user );
wp_safe_redirect( $redirect_to );
exit;
}
/**
* Should the login session persist between sessions.
*
* @return boolean
*/
public static function rememberme() {
$rememberme = false;
if ( ! empty( $_REQUEST['rememberme'] ) ) { //phpcs:ignore
$rememberme = true;
}
/**
* Changes the remember me value.
*
* @param bool $rememberme - Current state of the remember me variable.
*
* @since 2.0.0
*/
return (bool) apply_filters( WP_2FA_PREFIX . 'rememberme', $rememberme );
}
/**
* Prints the form that prompts the user to authenticate.
*
* @since 0.1-dev
*
* @param \WP_User $user \WP_User object of the logged-in user.
*/
public static function email_authentication_page( $user, $is_reset_protection = false ) {
if ( ! $user ) {
return;
}
$code_sent = (bool) User_Helper::get_meta( WP_2FA_PREFIX . 'code_sent' );
$use_default = ( 'use-custom' == WP2FA::get_wp2fa_white_label_setting( 'use_custom_2fa_message' ) ) ? 'custom-text-email-code-page' : 'default-text-code-page';
$text_to_display = ( $is_reset_protection ) ? 'default-text-pw-reset-code-page' : $use_default;
if ( ! $code_sent && ! isset( $_REQUEST[ self::INPUT_NAME_RESEND_CODE ] ) ) {
Setup_Wizard::send_authentication_setup_email( $user->ID, 'nominated_email_address', $is_reset_protection );
if ( ! empty( WP2FA::get_wp2fa_general_setting( 'brute_force_disable' ) ) ) {
User_Helper::set_meta( WP_2FA_PREFIX . 'code_sent', true );
}
}
require_once ABSPATH . '/wp-admin/includes/template.php';
?>
<?php echo WP2FA::get_wp2fa_white_label_setting( $text_to_display, true ); // phpcs:ignore ?>
<p>
</br>
<label for="authcode"><?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?></label>
<input type="tel" name="wp-2fa-email-code" id="authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off" />
<script>
const email_code = document.getElementById('authcode');
email_code.addEventListener('input', function() {
this.value = this.value.trim();
});
</script>
</p>
<?php
}
/**
* Validates the users input token.
*
* @since 0.1-dev
*
* @param \WP_User $user \WP_User object of the logged-in user.
* @return boolean
*/
public static function validate_email_authentication( $user ) {
if ( ! isset( $user->ID ) || ! isset( $_REQUEST['wp-2fa-email-code'] ) ) { //phpcs:ignore
return false;
}
return Authentication::validate_token( $user, \sanitize_text_field( \wp_unslash( $_REQUEST['wp-2fa-email-code'] ) ) );
}
/**
* Send the email code if missing or requested. Stop the authentication
* validation if a new token has been generated and sent.
*
* @param \WP_User $user \WP_User object of the logged-in user.
* @return boolean
*/
public static function pre_process_email_authentication( $user ) {
if ( isset( $user->ID ) && isset( $_REQUEST[ self::INPUT_NAME_RESEND_CODE ] ) ) { //phpcs:ignore -- nonce
Setup_Wizard::send_authentication_setup_email( $user->ID );
return true;
}
return false;
}
/**
* Prints the form that prompts the user to authenticate.
*
* @since 0.1-dev
*
* @param \WP_User $user \WP_User object of the logged-in user.
*/
public static function backup_codes_authentication_page( $user ) {
require_once ABSPATH . '/wp-admin/includes/template.php';
?>
<p><?php echo WP2FA::get_wp2fa_white_label_setting( 'default-backup-code-page', true ); // phpcs:ignore ?></p><br/>
<p>
<label for="authcode"><?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?></label>
<input type="tel" name="wp-2fa-backup-code" id="authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off" />
<script>
const backup_code = document.getElementById('authcode');
input.addEventListener('input', function() {
this.value = this.value.trim();
});
</script>
</p>
<?php
}
/**
* Removes GoDaddy style which causing the form elements to be shown
*
* @return void
*
* @since 2.2.0
*/
public static function dequeue_style() {
wp_dequeue_style( 'wpaas-sso-login' );
}
}
}
includes/classes/Authenticator/class-reset-passord.php 0000644 00000017554 15075513060 0017246 0 ustar 00 <?php
/**
* Responsible for WP2FA user's reset password forms.
*
* @package wp2fa
* @subpackage resetpassword
*
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @see https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Authenticator;
use WP2FA\Methods\Email;
use WP2FA\Authenticator\Login;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA\Admin\Controllers\Settings;
use WP2FA\Admin\Views\Password_Reset_2FA;
use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
/**
* Responsible for user login process.
*
* @since 2.5.0
*/
if ( ! class_exists( '\WP2FA\Authenticator\Reset_Password' ) ) {
/**
* Class for handling logins.
*/
class Reset_Password {
/**
* Show 2FA on password reset request.
*
* @param \WP_Error $errors A WP_Error object containing any errors generated
* by using invalid credentials.
* @param \WP_User|false $user_data WP_User object if found, false if the user does not exist.
*
* @return \WP_Error|void
*
* @since 2.5.0
*/
public static function lostpassword_post( $errors, $user_data ) {
if ( $errors->has_errors() ) {
return $errors;
}
if ( false === $user_data ) {
return $errors;
}
if ( ! ( $user_data instanceof \WP_User ) ) {
return $errors;
}
global $current_user;
if ( isset( $current_user ) && 0 !== $current_user->ID && $current_user->ID !== $user_data->ID ) {
return;
}
if ( class_exists( 'WP2FA\Extensions\RoleSettings\Role_Settings_Controller' ) ) {
$expire_action = Role_Settings_Controller::get_setting( User_Helper::get_user_role( $user_data ), Password_Reset_2FA::PASSWORD_RESET_SETTINGS_NAME, true );
} else {
$expire_action = Settings::get_role_or_default_setting( Password_Reset_2FA::PASSWORD_RESET_SETTINGS_NAME, null, null, true );
}
if ( 'password-reset-2fa' !== $expire_action ) {
return $errors;
}
if ( User_Helper::get_reset_password_valid_for_user() ) {
return $errors;
}
$login_nonce = Login::create_login_nonce( $user_data->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
self::show_two_factor_login( $user_data, $login_nonce['key'] );
exit;
}
/**
* Generates the html form for the second step of the authentication process.
*
* @param \WP_User $user \WP_User object of the logged-in user.
* @param string $login_nonce - The generated nonce.
* @param string $error_msg - Error message (if any) to show.
*
* @since 2.5.0
*/
public static function show_two_factor_login( $user, $login_nonce, $error_msg = '' ) {
if ( ! function_exists( 'login_header' ) ) {
// We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
include_once WP_2FA_PATH . 'includes/functions/login-header.php';
}
$lostpassword_redirect = ! empty( $_REQUEST['redirect_to'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['redirect_to'] ) ) : '';
/**
* Filters the URL redirected to after submitting the lostpassword/retrievepassword form.
*
* @since 3.0.0
*
* @param string $lostpassword_redirect The redirect destination URL.
*/
$redirect_to = apply_filters( 'lostpassword_redirect', $lostpassword_redirect );
if ( ! function_exists( 'login_header' ) ) {
// We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
include_once WP_2FA_PATH . 'includes/functions/login-header.php';
}
login_header();
if ( ! empty( $error_msg ) ) {
echo '<div id="login_error"><strong>' . \esc_html( apply_filters( 'login_errors', \esc_html( $error_msg ) ) ) . '</strong><br /></div>';
}
?>
<form name="lostpasswordform" id="lostpasswordform" action="<?php echo \esc_url( network_site_url( 'wp-login.php?action=lostpassword', 'login_post' ) ); ?>" method="post">
<input type="hidden" name="wp-auth-id" id="wp-auth-id" value="<?php echo \esc_attr( $user->ID ); ?>" />
<input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="<?php echo \esc_attr( $login_nonce ); ?>" />
<input type="hidden" name="reset" id="reset" value="<?php echo \esc_attr( 'reset-2fa' ); ?>" />
<input type="hidden" name="redirect_to" value="<?php echo \esc_attr( $redirect_to ); ?>" />
<?php
// Check to see what provider is set and give the relevant authentication page.
Login::email_authentication_page( $user, true );
?>
<p>
<?php
/**
* Using that filter, the default text of the login button could be changed
*
* @param callback - Callback function which is responsible for text manipulation.
*
* @since 2.0.0
*/
$button_text = apply_filters( WP_2FA_PREFIX . 'new_password_button_text', \esc_html__( 'Get New Password', 'wp-2fa' ) );
?>
<p class="submit">
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="<?php echo \esc_attr( $button_text ); ?>" />
</p>
</p>
<p class="2fa-email-resend">
<input type="submit" class="button"
name="<?php echo \esc_attr( Login::INPUT_NAME_RESEND_CODE ); ?>"
value="<?php \esc_attr_e( 'Resend Code', 'wp-2fa' ); ?>"/>
</p>
</form>
<?php
if ( function_exists( 'login_footer' ) ) {
\login_footer( 'user_login' );
}
}
/**
* Login form validation.
*
* @since 2.5.0
*
* @SuppressWarnings(PHPMD.ExitExpression)
*/
public static function login_form_validate_2fa() {
if ( ! isset( $_POST['wp-auth-id'], $_POST['wp-auth-nonce'], $_POST['reset'] ) ) { // phpcs:ignore
return;
}
$auth_id = (int) $_POST['wp-auth-id']; // phpcs:ignore
$user = get_userdata( $auth_id );
if ( ! $user ) {
return;
}
$nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : ''; // phpcs:ignore
if ( true !== Login::verify_login_nonce( $user->ID, $nonce ) ) {
wp_safe_redirect( get_bloginfo( 'url' ) );
exit;
}
$provider = Email::METHOD_NAME;
// If this is an email login, or if the user failed validation previously, lets send the code to the user.
if ( Email::METHOD_NAME === $provider && true !== Login::pre_process_email_authentication( $user ) ) {
$login_nonce = Login::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
}
// Validate Email.
if ( Email::METHOD_NAME === $provider && true !== Login::validate_email_authentication( $user ) ) {
do_action(
'wp_login_failed',
$user->user_login,
new \WP_Error(
'authentication_failed',
__( '<strong>Error</strong>: User can not be authenticated.', 'wp-2fa' )
)
);
$login_nonce = Login::create_login_nonce( $user->ID );
if ( ! $login_nonce ) {
wp_die( \esc_html__( 'Failed to create a login nonce.', 'wp-2fa' ) );
}
if ( isset( $_REQUEST['wp-2fa-email-code-resend'] ) ) { //phpcs:ignore
self::show_two_factor_login( $user, $login_nonce['key'], \esc_html__( 'A new code has been sent.', 'wp-2fa' ), $provider ); // phpcs:ignore
} else {
self::show_two_factor_login( $user, $login_nonce['key'], \esc_html__( 'ERROR: Invalid verification code.', 'wp-2fa' ), $provider ); // phpcs:ignore
}
exit;
}
User_Helper::set_reset_password_valid_for_user( true );
$errors = retrieve_password( $user->user_email );
if ( ! is_wp_error( $errors ) ) {
$redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['redirect_to'] ) ) : 'wp-login.php?checkemail=confirm';
User_Helper::remove_reset_password_valid_for_user();
wp_safe_redirect( $redirect_to );
exit;
}
\wp_redirect( site_url( 'wp-login.php?action=lostpassword' ) );
}
}
}
includes/classes/Authenticator/class-open-ssl.php 0000644 00000011443 15075513060 0016202 0 ustar 00 <?php
/**
* Open SSL encrypt / decrypt class.
*
* @package wp2fa
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
declare(strict_types=1);
namespace WP2FA\Authenticator;
use WP2FA\WP2FA;
use WP2FA\Utils\Debugging;
use function WP2FA\Core\wp_salt;
/**
* Open_SSL - Class for encryption and decryption of the string using open_ssl method
*
* @since 2.0.0
*/
if ( ! class_exists( '\WP2FA\Authenticator\Open_SSL' ) ) {
/**
* Responsible for SSL operations
*/
class Open_SSL {
const CIPHER_METHOD = 'aes-256-ctr';
const BLOCK_BYTE_SIZE = 16;
const DIGEST_ALGORITHM = 'SHA256';
const SECRET_KEY_PREFIX = 'lsc_';
/**
* Internal cache var for the PHP ssl functions availability
*
* @var mixed|boolean
*
* @since 2.0.0
*/
private static $ssl_enabled = null;
/**
* Encrypts given text
*
* @param string $text - Text to be encrypted.
*
* @return string
*
* @since 2.0.0
*/
public static function encrypt( string $text ): string {
if ( self::is_ssl_available() ) {
$iv = self::secure_random( self::BLOCK_BYTE_SIZE );
$key = \openssl_digest( \base64_decode( wp_salt() ), self::DIGEST_ALGORITHM, true ); //phpcs:ignore
$text = \openssl_encrypt(
$text,
self::CIPHER_METHOD,
$key,
OPENSSL_RAW_DATA,
$iv
);
$text = \base64_encode( $iv . $text ); //phpcs:ignore
}
return $text;
}
/**
* Decrypts crypt text
*
* @param string $text - Encrypted text to be decrypted.
*
* @return string
*
* @since 2.0.0
*/
public static function decrypt( string $text ): string {
Debugging::log( 'Decrypting a text: ' . $text );
Debugging::log( 'Will use the following salt: ' . wp_salt() );
if ( self::is_ssl_available() ) {
$decoded_base = \base64_decode( $text ); //phpcs:ignore
$key = \openssl_digest( \base64_decode( wp_salt() ), self::DIGEST_ALGORITHM, true ); //phpcs:ignore
$ivlen = \openssl_cipher_iv_length( self::CIPHER_METHOD );
$iv = \substr( $decoded_base, 0, $ivlen );
$ciphertext_raw = \substr( $decoded_base, $ivlen );
$text = \openssl_decrypt( $ciphertext_raw, self::CIPHER_METHOD, $key, OPENSSL_RAW_DATA, $iv );
}
Debugging::log( 'Decrypted text: ' . $text );
return $text;
}
/**
* Decrypts crypt text
*
* @param string $text - Encrypted text to be decrypted.
*
* @return string
*
* @since 2.0.0
*/
public static function decrypt_legacy( string $text ): string {
Debugging::log( 'Decrypting a text: ' . $text );
if ( self::is_ssl_available() ) {
$decoded_base = \base64_decode( $text ); //phpcs:ignore
$key = \openssl_digest( \base64_decode( WP2FA::get_secret_key() ), self::DIGEST_ALGORITHM, true ); //phpcs:ignore
$ivlen = \openssl_cipher_iv_length( self::CIPHER_METHOD );
$iv = \substr( $decoded_base, 0, $ivlen );
$ciphertext_raw = \substr( $decoded_base, $ivlen );
$text = \openssl_decrypt( $ciphertext_raw, self::CIPHER_METHOD, $key, OPENSSL_RAW_DATA, $iv );
}
Debugging::log( 'Decrypted text: ' . $text );
return $text;
}
/**
* Decrypts old wps_ secret strings
*
* @param string $text - The encrypted string.
*
* @return string
*
* @since 2.3.0
*/
public static function decrypt_wps( string $text ): string {
Debugging::log( 'Decrypting a text: ' . $text );
Debugging::log( 'Will use the following salt: ' . \wp_salt() );
if ( self::is_ssl_available() ) {
$decoded_base = \base64_decode( $text ); //phpcs:ignore
$key = \openssl_digest( \base64_decode( \wp_salt() ), self::DIGEST_ALGORITHM, true ); //phpcs:ignore
$ivlen = \openssl_cipher_iv_length( self::CIPHER_METHOD );
$iv = \substr( $decoded_base, 0, $ivlen );
$ciphertext_raw = \substr( $decoded_base, $ivlen );
$text = \openssl_decrypt( $ciphertext_raw, self::CIPHER_METHOD, $key, OPENSSL_RAW_DATA, $iv );
}
Debugging::log( 'Decrypted text: ' . $text );
return $text;
}
/**
* Generates random bytes by given size
*
* @param integer $octets - Number of octets for use for random generator.
*
* @return string
*
* @since 2.0.0
*/
public static function secure_random( int $octets = 0 ): string {
if ( 0 === $octets ) {
$octets = self::BLOCK_BYTE_SIZE;
}
return \random_bytes( $octets );
}
/**
* Checks the open ssl methods existence
*
* @return boolean
*
* @since 2.0.0
*/
public static function is_ssl_available(): bool {
if ( null === self::$ssl_enabled ) {
self::$ssl_enabled = false;
if ( \function_exists( 'openssl_encrypt' ) ) {
self::$ssl_enabled = true;
}
}
return self::$ssl_enabled;
}
}
}
includes/classes/Authenticator/class-authentication.php 0000644 00000034056 15075513060 0017466 0 ustar 00 <?php
/**
* Responsible for WP2FA user's authentication.
*
* @package wp2fa
* @subpackage authentication
* @copyright 2024 Melapress
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wordpress.org/plugins/wp-2fa/
*/
/**
* Class for handling general authentication tasks.
*
* @since 0.1-dev
*
* @package WP2FA
*/
declare(strict_types=1);
namespace WP2FA\Authenticator;
use WP2FA\Authenticator\Open_SSL;
use WP2FA\Admin\Helpers\User_Helper;
use WP2FA_Vendor\Endroid\QrCode\QrCode;
use WP2FA\Admin\Methods\Traits\Login_Attempts;
use WP2FA_Vendor\Endroid\QrCode\Writer\SvgWriter;
if ( ! class_exists( '\WP2FA\Authenticator\Authentication' ) ) {
/**
* Authenticator class
*/
class Authentication {
use Login_Attempts;
const DEFAULT_KEY_BIT_SIZE = 160;
const DEFAULT_CRYPTO = 'sha1';
const DEFAULT_DIGIT_COUNT = 6;
const DEFAULT_TIME_STEP_SEC = 30;
const DEFAULT_TIME_STEP_ALLOWANCE = 4;
/**
* Holds the name of the meta key for the allowed login attempts
*
* @var string
*
* @since 2.0.0
*/
private static $logging_attempts_meta_key = WP_2FA_PREFIX . 'email-login-attempts';
/**
* The login attempts class
*
* @var \WP2FA\Admin\Controllers\Login_Attempts
*
* @since 2.0.0
*/
private static $login_attempts = null;
/**
* String with the base32 characters
*
* @var string
*/
private static $base_32_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
/**
* String with the decrypted key
*
* @var string
*/
private static $decrypted_key = '';
/**
* Generate QR code
*
* @param string $name Username.
* @param string $key Auth key.
* @param string $title Site title.
* @return string QR code URL.
*/
public static function get_google_qr_code( $name, $key, $title = null ) {
// Encode to support spaces, question marks and other characters.
$name = rawurlencode( $name );
self::decrypt_key_if_needed( $key );
$target_url = ( 'otpauth://totp/' . $name . '?secret=' . $key );
if ( isset( $title ) ) {
$target_url .= ( '&issuer=' . rawurlencode( $title ) );
}
$qr = new QrCode( $target_url );
$qr->setWriterOptions( array( 'exclude_xml_declaration' => true ) );
$writer = new SvgWriter();
$result = $writer->writeString( $qr );
return 'data:image/svg+xml;base64,' . base64_encode( $result ); // phpcs:ignore
}
/**
* Generates key
*
* @param int $bitsize Number of bits to use for key.
*
* @return string $bitsize long string composed of available base32 chars.
*/
public static function generate_key( $bitsize = self::DEFAULT_KEY_BIT_SIZE ) {
$bytes = ceil( $bitsize / 8 );
$secret = wp_generate_password( $bytes, true, true );
$secret = Open_SSL::encrypt( self::base32_encode( $secret ) );
if ( Open_SSL::is_ssl_available() ) {
$secret = Open_SSL::SECRET_KEY_PREFIX . $secret;
}
return $secret;
}
/**
* Generates salt for the site
*
* @return string
*
* @since 2.4.0
*
* @throws \RuntimeException - throw exception if the generated string has unexpected characters or not a string.
*/
public static function generate_salt(): string {
$secret = \wp_generate_password( 64, true, true );
if ( ! is_string( $secret ) || strlen( $secret ) !== 64 ) {
throw new \RuntimeException( 'Could not generate secret key.' );
}
return base64_encode( $secret );
}
/**
* Returns a base32 encoded string.
*
* @param string $string String to be encoded using base32.
*
* @return string base32 encoded string without padding.
*/
public static function base32_encode( $string ) {
if ( empty( $string ) ) {
return '';
}
$binary_string = '';
foreach ( str_split( $string ) as $character ) {
$binary_string .= str_pad( base_convert( (string) ord( $character ), 10, 2 ), 8, '0', STR_PAD_LEFT );
}
$five_bit_sections = str_split( $binary_string, 5 );
$base32_string = '';
foreach ( $five_bit_sections as $five_bit_section ) {
$base32_string .= self::$base_32_chars[ base_convert( str_pad( $five_bit_section, 5, '0' ), 2, 10 ) ];
}
return $base32_string;
}
/**
* Clears the value of the decrypted key
*
* @return void
*
* @since 2.6.0
*/
public static function clear_decrypted_key() {
self::$decrypted_key = '';
}
/**
* Check if the TOTP secret key has a proper format.
*
* @param string $key TOTP secret key.
*
* @return boolean
*/
public static function is_valid_key( $key ) {
self::decrypt_key_if_needed( $key );
$check = sprintf( '/^[%s]+$/', self::$base_32_chars );
if ( 1 === preg_match( $check, $key ) ) {
return true;
}
return false;
}
/**
* Checks if a given code is valid for a given key, allowing for a certain amount of time drift
*
* @param string $key The share secret key to use.
* @param string $authcode The code to test.
*
* @return bool Whether the code is valid within the time frame
*/
public static function is_valid_authcode( $key, $authcode ) {
self::decrypt_key_if_needed( $key );
/**
* That allows to change the amount of thick for decrypting the key.
*
* @param bool - Default at this point is true - no method is selected.
*
* @since 2.0.0
*/
$max_ticks = apply_filters( WP_2FA_PREFIX . 'totp_time_step_allowance', self::DEFAULT_TIME_STEP_ALLOWANCE );
// Array of all ticks to allow, sorted using absolute value to test closest match first.
$ticks = range( - $max_ticks, $max_ticks );
usort( $ticks, array( __CLASS__, 'abssort' ) );
$time = time() / self::DEFAULT_TIME_STEP_SEC;
foreach ( $ticks as $offset ) {
$log_time = $time + $offset;
$calculdated = (string) self::calc_totp( $key, $log_time );
if ( hash_equals( $calculdated, $authcode ) ) {
return true;
}
}
return false;
}
/**
* Calculate a valid code given the shared secret key
*
* @param string $key The shared secret key to use for calculating code.
* @param mixed $step_count The time step used to calculate the code, which is the floor of time() divided by step size.
* @param int $digits The number of digits in the returned code.
* @param string $hash The hash used to calculate the code.
* @param int $time_step The size of the time step.
*
* @return string The totp code
*/
public static function calc_totp( $key, $step_count = false, $digits = self::DEFAULT_DIGIT_COUNT, $hash = self::DEFAULT_CRYPTO, $time_step = self::DEFAULT_TIME_STEP_SEC ) {
$secret = self::base32_decode( $key );
if ( false === $step_count ) {
$step_count = floor( time() / $time_step );
}
$timestamp = self::pack64( $step_count );
$hash = hash_hmac( $hash, $timestamp, $secret, true );
$offset = ord( $hash[19] ) & 0xf;
$code = (
( ( ord( $hash[ $offset + 0 ] ) & 0x7f ) << 24 ) |
( ( ord( $hash[ $offset + 1 ] ) & 0xff ) << 16 ) |
( ( ord( $hash[ $offset + 2 ] ) & 0xff ) << 8 ) |
( ord( $hash[ $offset + 3 ] ) & 0xff )
) % pow( 10, $digits );
return str_pad( (string) $code, $digits, '0', STR_PAD_LEFT );
}
/**
* Decode a base32 string and return a binary representation
*
* @param string $base32_string The base 32 string to decode.
*
* @throws \Exception If string contains non-base32 characters.
*
* @return string Binary representation of decoded string
*/
public static function base32_decode( $base32_string ) {
$base32_string = strtoupper( $base32_string );
if ( ! preg_match( '/^[' . self::$base_32_chars . ']+$/', $base32_string, $match ) ) {
throw new \Exception( 'Invalid characters in the base32 string.' );
}
$l = strlen( $base32_string );
$n = 0;
$j = 0;
$binary = '';
for ( $i = 0; $i < $l; $i++ ) {
$n = $n << 5; // Move buffer left by 5 to make room.
$n = $n + strpos( self::$base_32_chars, $base32_string[ $i ] ); // Add value into buffer.
$j += 5; // Keep track of number of bits in buffer.
if ( $j >= 8 ) {
$j -= 8;
$binary .= chr( ( $n & ( 0xFF << $j ) ) >> $j );
}
}
return $binary;
}
/**
* Used with usort to sort an array by distance from 0
*
* @param int $a First array element.
* @param int $b Second array element.
*
* @return int -1, 0, or 1 as needed by usort
*/
private static function abssort( $a, $b ) {
$a = abs( $a );
$b = abs( $b );
if ( $a === $b ) {
return 0;
}
return ( $a < $b ) ? -1 : 1;
}
/**
* Pack stuff
*
* @param string $value The value to be packed.
*
* @return string Binary packed string.
*/
public static function pack64( $value ) {
// 64bit mode (PHP_INT_SIZE == 8).
if ( PHP_INT_SIZE >= 8 ) {
// If we're on PHP 5.6.3+ we can use the new 64bit pack functionality.
if ( version_compare( PHP_VERSION, '5.6.3', '>=' ) && PHP_INT_SIZE >= 8 ) {
return pack( 'J', $value );
}
$highmap = 0xffffffff << 32;
$higher = ( $value & $highmap ) >> 32;
} else {
/*
* 32bit PHP can't shift 32 bits like that, so we have to assume 0 for the higher
* and not pack anything beyond it's limits.
*/
$higher = 0;
}
$lowmap = 0xffffffff;
$lower = $value & $lowmap;
return pack( 'NN', $higher, $lower );
}
/**
* Generate a random eight-digit string to send out as an auth code.
*
* @since 0.1-dev
*
* @param int $length The code length.
* @param string|array $chars Valid auth code characters.
* @return string
*/
public static function get_code( $length = 6, $chars = '1234567890' ) {
$code = '';
if ( is_array( $chars ) ) {
$chars = implode( '', $chars );
}
for ( $i = 0; $i < $length; $i++ ) {
$code .= substr( $chars, wp_rand( 0, strlen( $chars ) - 1 ), 1 );
}
return $code;
}
/**
* Generate the user token.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
* @return string
*/
public static function generate_token( $user_id ) {
$token = self::get_code();
User_Helper::set_email_token_for_user( \wp_hash( $token ), $user_id );
return $token;
}
/**
* Validate the user token.
*
* @since 0.1-dev
*
* @param \WP_User $user User ID.
* @param string $token User token.
*
* @return boolean
*/
public static function validate_token( $user, $token ) {
$user_id = $user->ID;
$hashed_token = self::get_user_token( $user_id );
// Bail if token is empty or it doesn't match.
// This code is here just because people have no idea what is the difference between preaching and real life.
if ( empty( $hashed_token ) || ( ! hash_equals( wp_hash( $token ), $hashed_token ) ) ) {
self::increase_login_attempts( $user );
return false;
}
// Ensure that the token can't be re-used.
self::delete_token( $user_id );
self::clear_login_attempts( $user );
\delete_transient( 'wp_2fa_code_login_' . $user_id );
return true;
}
/**
* Delete the user token.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
*/
public static function delete_token( $user_id ) {
User_Helper::remove_email_token_for_user( $user_id );
}
/**
* Check if user has a valid token already.
*
* @param int $user_id User ID.
* @return boolean If user has a valid email token.
*/
public static function user_has_token( $user_id ) {
$hashed_token = self::get_user_token( $user_id );
if ( ! empty( $hashed_token ) ) {
return true;
} else {
return false;
}
}
/**
* Get the authentication token for the user.
*
* @param int $user_id User ID.
*
* @return string|boolean User token or `false` if no token found.
*/
public static function get_user_token( $user_id ) {
$hashed_token = User_Helper::get_email_token_for_user( $user_id );
if ( ! empty( $hashed_token ) && is_string( $hashed_token ) ) {
return $hashed_token;
}
return false;
}
/**
* Returns list of all the auth apps and their properties
*
* @return array
*/
public static function get_apps(): array {
return array(
'authy' => array(
'logo' => 'authy-logo.png',
'hash' => 'authy',
'name' => 'Authy',
),
'google' => array(
'logo' => 'google-logo.png',
'hash' => 'google',
'name' => 'Google Authenticator',
),
'microsoft' => array(
'logo' => 'microsoft-logo.png',
'hash' => 'microsoft',
'name' => 'Microsoft Authenticator',
),
'duo' => array(
'logo' => 'duo-logo.png',
'hash' => 'duo',
'name' => 'Duo Security',
),
'lastpass' => array(
'logo' => 'lastpass-logo.png',
'hash' => 'lastpass',
'name' => 'LastPass',
),
'freeotp' => array(
'logo' => 'free-otp-logo.png',
'hash' => 'freeotp',
'name' => 'FreeOTP',
),
'okta' => array(
'logo' => 'okta-logo.png',
'hash' => 'okta',
'name' => 'Okta',
),
);
}
/**
* Getter for the base32 character set
*
* @return string
*
* @since 2.0.0
*/
public static function get_base32_characters(): string {
return self::$base_32_chars;
}
/**
* Validates base32 encoded string
*
* @param string $text = The text to be validated.
*
* @return boolean
*
* @since 2.0.0
*/
public static function validate_base32_string( string $text ): bool {
if ( ! preg_match( '/^[' . self::$base_32_chars . ']+$/', $text, $match ) ) {
return false;
}
return true;
}
/**
* Checks the given key and decrypts it if necessarily
*
* @param string $key - The key to check.
*
* @return string
*
* @since 2.0.0
*/
public static function decrypt_key_if_needed( string &$key ): string {
if ( '' === trim( (string) self::$decrypted_key ) ) {
if ( Open_SSL::is_ssl_available() && false !== \strpos( $key, Open_SSL::SECRET_KEY_PREFIX ) ) {
$key = self::$decrypted_key = Open_SSL::decrypt( substr( $key, 4 ) ); // phpcs:ignore
} else {
self::$decrypted_key = $key;
}
}
return ( $key = self::$decrypted_key ); // phpcs:ignore
}
}
}
includes/classes/Authenticator/index.php 0000644 00000000046 15075513060 0014443 0 ustar 00 <?php
/**
* Nothing to see here.
*/
includes/classes/index.php 0000644 00000000046 15075513060 0011631 0 ustar 00 <?php
/**
* Nothing to see here.
*/
wp-2fa.php 0000644 00000014172 15075513060 0006360 0 ustar 00 <?php
/**
* WP 2FA - Two-factor authentication for WordPress .
*
* @copyright Copyright (C) 2013-2024, Melapress - support@melapress.com
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 or higher
*
* @wordpress-plugin
* Plugin Name: WP 2FA - Two-factor authentication for WordPress
* Version: 2.8.0
* Plugin URI: https://melapress.com/
* Description: Easily add an additional layer of security to your WordPress login pages. Enable Two-Factor Authentication for you and all your website users with this easy to use plugin.
* Author: Melapress
* Author URI: https://melapress.com/
* Text Domain: wp-2fa
* Domain Path: /languages/
* License: GPL v3
* Requires at least: 5.0
* Requires PHP: 7.3
* Network: true
*
* @package WP2FA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @fs_ignore /dist/, /extensions/, /freemius/, /includes/, /languages/, /third-party/, /vendor/
*/
use WP2FA\WP2FA;
use WP2FA\Utils\Migration;
use WP2FA\Extensions_Loader;
use WP2FA\Admin\Helpers\WP_Helper;
use WP2FA\Freemius\Freemius_Helper;
use WP2FA\Admin\Helpers\File_Writer;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( defined( '\DISABLE_2FA_LOGIN' ) && \DISABLE_2FA_LOGIN ) {
return;
}
// Useful global constants.
if ( ! defined( 'WP_2FA_VERSION' ) ) {
define( 'WP_2FA_VERSION', '2.8.0' );
define( 'WP_2FA_BASE', plugin_basename( __FILE__ ) );
define( 'WP_2FA_URL', plugin_dir_url( __FILE__ ) );
define( 'WP_2FA_PATH', WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . dirname( WP_2FA_BASE ) . DIRECTORY_SEPARATOR );
define( 'WP_2FA_INC', WP_2FA_PATH . 'includes/' );
define( 'WP_2FA_FILE', __FILE__ );
define( 'WP_2FA_LOGS_DIR', 'wp-2fa-logs' );
// Prefix used in usermetas, settings and transients.
define( 'WP_2FA_PREFIX', 'wp_2fa_' );
define( 'WP_2FA_POLICY_SETTINGS_NAME', WP_2FA_PREFIX . 'policy' );
define( 'WP_2FA_SETTINGS_NAME', WP_2FA_PREFIX . 'settings' );
define( 'WP_2FA_WHITE_LABEL_SETTINGS_NAME', WP_2FA_PREFIX . 'white_label' );
define( 'WP_2FA_EMAIL_SETTINGS_NAME', WP_2FA_PREFIX . 'email_settings' );
define( 'WP_2FA_PREFIX_PAGE', 'wp-2fa-' );
}
// phpcs:disable
// phpcs:enable
// Include files.
require_once WP_2FA_INC . 'functions/core.php';
// Require Composer autoloader if it exists.
if ( file_exists( WP_2FA_PATH . 'vendor/autoload.php' ) ) {
require_once WP_2FA_PATH . 'vendor/autoload.php';
}
// run any required update routines.
Migration::migrate();
// Setup_Wizard.
if ( WP_Helper::is_multisite() ) {
add_action( 'network_admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'network_admin_menus' ), 10 );
add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
} else {
add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
}
// Activation/Deactivation.
register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
// Register our uninstallation hook.
register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
add_filter( 'plugins_loaded', array( '\WP2FA\WP2FA', 'init' ) );
add_action( 'plugins_loaded', array( '\WP2FA\WP2FA', 'add_wizard_actions' ), 10 );
// phpcs:disable
// phpcs:enable
if ( ! defined( File_Writer::SECRET_NAME ) ) {
define( File_Writer::SECRET_NAME, WP2FA::get_secret_key() );
define( 'WP2FA_SECRET_IS_IN_DB', true );
}
// phpcs:disable
/* @free:start */
// phpcs:enable
if ( ! function_exists( 'wp2fa_free_on_plugin_activation' ) ) {
/**
* Takes care of deactivation of the premium plugin when the free plugin is activated.
*
* Note: This code MUST NOT be present in the premium version an is removed automatically during the build process.
*
* @since 2.0.0
*/
function wp2fa_free_on_plugin_activation() {
$premium_version_slug = 'wp-2fa-premium/wp-2fa.php';
if ( is_plugin_active( $premium_version_slug ) ) {
deactivate_plugins( $premium_version_slug, true );
}
check_ssl();
}
register_activation_hook( __FILE__, 'wp2fa_free_on_plugin_activation' );
}
// phpcs:disable
/* @free:end */
// phpcs:enable
/*
* Clears the config cache from the DB
*
* @return void
*
* @since 2.2.0
*/
add_action(
'upgrader_process_complete',
function () {
delete_transient( 'wp_2fa_config_file_hash' );
},
10,
2
);
if ( ! function_exists( 'check_ssl' ) ) {
/**
* Checks if the required library is installed and cancels the process if not.
*
* @return void
*
* @since 2.2.0
*/
function check_ssl() {
if ( ! \WP2FA\Authenticator\Open_SSL::is_ssl_available() ) {
$html = '<div class="updated notice is-dismissible">
<p>' . \esc_html__( 'This plugin requires OpenSSL. Contact your web host or website administrator so they can enable OpenSSL. Re-activate the plugin once the library has been enabled.', 'wp-2fa' )
. '</p>
</div>';
echo $html; // phpcs:ignore
exit();
}
}
}
if ( \PHP_VERSION_ID < 80000 && ! \interface_exists( 'Stringable' ) ) {
interface Stringable { // phpcs:ignore
/**
* Mockup function for PHP versions lower than 8.
*
* @return string
*/
public function __toString();
}
}
if ( ! function_exists( 'str_starts_with' ) ) {
/**
* PHP lower than 8 is missing that function but it required in the newer versions of our plugin.
*
* @param string $haystack - The string to search in.
* @param string $needle - The needle to search for.
*
* @return bool
*
* @since 2.6.4
*/
function str_starts_with( $haystack, $needle ): bool {
if ( '' === $needle ) {
return true;
}
return 0 === strpos( $haystack, $needle );
}
}
readme.txt 0000644 00000026307 15075513060 0006554 0 ustar 00 === WP 2FA - Two-factor authentication for WordPress ===
Contributors: Melapress, robert681
Plugin URI: https://melapress.com/wordpress-2fa/
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl.html
Tags: 2FA, two-factor authentication, multi step authentication, 2-factor authentication, WordPress authentication, two step authentication
Requires at least: 5.0
Tested up to: 6.6.2
Stable tag: 2.8.0
Requires PHP: 7.3.0
Harden your website's authentication; add two-factor authentication (2FA) for all your users with this easy-to-use plugin.
== Description ==
### A free and easy-to-use two-factor authentication plugin for WordPress
Add an extra layer of security to your WordPress website login pages and protect your users. Enable [two-factor authentication (2FA)](https://melapress.com/wordpress-2fa/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa), the best protection against users using weak passwords, automated password guessing, and brute force attacks.
[youtube https://www.youtube.com/watch?v=vRlX_NNGeFo]
[Features](https://melapress.com/wordpress-2fa/features/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa) | [Getting Started](https://melapress.com/support/kb/wp-2fa-plugin-getting-started/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa) | [Get the Premium!](https://melapress.com/wordpress-2fa/pricing/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
Use the WP 2FA plugin to enable two-factor authentication for your WordPress administrator, and to enforce your website users, or users with a specific role to use 2FA. This plugin is very easy to use; everything can be configured via wizards with clear instructions, so even non technical users can setup 2FA without requiring technical assistance.
#### MAINTAINED & SUPPORTED BY MELAPRESS
Melapress develops high-quality WordPress management and security plugins such as [Melapress Login Security](https://melapress.com/wordpress-login-security/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa), [CAPTCHA 4WP](https://melapress.com/wordpress-captcha/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa), and [WP Activity Log](https://melapress.com/wordpress-activity-log/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa), the #1 user-rated activity log plugin for WordPress.
Browse our list of [WordPress security and administration plugins](https://melapress.com/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa) to see how our plugins can help you better manage and improve the security and administration of your WordPress websites and users.
### WP 2FA key plugin features and capabilities
- Free Two-factor authentication (2FA) for all users
- Supports multiple 2FA methods
- An API that allows you to integrate supplementary 2FA methods
- Universal 2FA app support – generate codes from Google Authenticator, Authy & any other 2FA app
- Supports 2FA backup methods
- Wizard-driven plugin configuration & 2FA setup – no technical knowledge required
- Use 2FA policies to enforce 2FA with a grace period or require users to instantly setup 2FA upon logging in
- No WordPress dashboard access is required for users to set up 2FA
- Fully editable email templates
- Protection against automated password & dictionary attacks
- Much more
### Upgrade to WP 2FA Premium and get even more
The premium version of WP 2FA comes bundled with even more features to take your WordPress website login security to the next level.
With the premium edition of WP 2FA, you get more 2FA methods, 1-click integration with WooCommerce, trusted devices feature, extensive white labeling capabilities, and much more!
### Premium features list
- Everything in the free version
- Full white labeling capabilities (change all the text and look and feel in wizards, emails, SMS and 2FA pages)
- [YubiKey hardware key support](https://melapress.com/support/kb/wp-2fa-hardware-key/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
- Several other additional 2FA methods (such as 2FA over SMS, link in email & more)
- [Trusted devices](https://melapress.com/support/kb/wp-2fa-configure-2fa-trusted-devices/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa) (no 2FA required for a configured period of time)
- Require 2FA on password reset
- One-click integration to set up [WooCommerce and two-factor authentication (2FA)](https://melapress.com/woocommerce-2fa/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
- Much more
Refer to the [WP 2FA plugin features and benefits page](https://melapress.com/wordpress-2fa/features/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa) to learn more about the benefits of upgrading to WP 2FA Premium.
## Free and premium support
Premium world-class support for WP 2FA is free via email or through the WordPress support forums.
Note: paid customer support is given priority and is provided via one-to-one email. Upgrade to Premium to benefit from priority support.
For any other queries, feedback, or if you simply want to get in touch with us, please use our [contact form](https://melapress.com/contact/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa).
## As featured on:
- [WP Beginner](https://www.wpbeginner.com/plugins/how-to-add-two-factor-authentication-for-wordpress/)
- [IsitWP](https://www.isitwp.com/best-wordpress-security-authentication-plugins/)
- [WP Astra](https://wpastra.com/two-factor-authentication-wordpress/)
- [MainWP](https://mainwp.com/how-to-use-the-wp-2fa-plugin-on-your-child-sites/)
- [FixRunner](https://www.fixrunner.com/wordpress-two-factor-authentication/)
- [Inmotion Hosting](https://www.inmotionhosting.com/support/edu/wordpress/plugins/wp-2fa/)
- [WP Marmite](https://wpmarmite.com/en/wordpress-two-factor-authentication/)
## Related links and documentation:
You can find more detailed information about 2FA and its benefits in the links below
- [The benefits of using 2FA on WordPress](https://melapress.com/benefits-2fa-wordpress/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
- [Beginner’s guide to two-factor authentication](https://melapress.com/what-is-2fa-beginners-guide/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
- [Setting up Google Authenticator for WordPress 2FA](https://melapress.com/google-authenticator-app-wordpress-2fa/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
- [List of supported 2FA apps](https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
- [The definitive guide to WordPress security](https://melapress.com/wordpress-security/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
- [Official Melapress website](https://melapress.com/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa)
== Installing WP 2FA ==
###From within WordPress
1. Navigate to ‘Plugins > Add New’
2. Search for ‘WP 2FA’
3. Install & activate WP 2FA from your Plugins page
###Manually
1. Download the plugin from the WordPress plugins repository
2. Unzip the zip file and upload the folder to the /wp-content/plugins/ directory
3. Activate the WP 2FA plugin through the ‘Plugins’ menu in WordPress
== Frequently Asked Questions ==
= Does the plugin send any data to Melapress? =
No, the plugin does not send any data to us whatsoever. The only data we recieve is license data from the premium edition of the plugin.
= Does the plugin receive updates? =
We update the plugin fairly regularly to ensure the plugin continues to run in tip-top shape while adding new features from time to time.
= Support and Documentation =
Please refer to our [support pages](https://melapress.com/support/?utm_source=wp+repo&utm_medium=repo+link&utm_campaign=wordpress_org&utm_content=wp2fa) for all the technical and product documentation.
= How can I report security bugs? =
You can report security bugs through the Patchstack Vulnerability Disclosure Program. Please use this [form](https://patchstack.com/database/vdp/wp-2fa). For more details please refer to our [Melapress plugins security program](https://melapress.com/plugins-security-program/).
== Screenshots ==
1. The first-time install wizard allows you to setup 2FA on your website and for your user within seconds.
2. The wizards make setting up 2FA very easy, so even non technical users can setup 2FA without requiring help.
3. You can require users to enable 2FA and also give them a grace period to do so.
4. Users can also use one-time codes via email as a two-factor authentication method.
5. You can use policies to require users to instantly set up and use 2FA, so the next time they login they will be prompted with this.
6. You can give users a grace period until they configure 2FA. You can also specify what should the plugin do once the grace period is over.
7. It is recommended for all users to also generate backup codes, in case they cannot access the primary device.
8. In the user profile users only have a few 2FA options, so it is not confusing for them and everything is self explanatory.
== Changelog ==
= 2.8.0 (2024-07-17) =
* **New features**
* Out of the box support for Yubico - [use any YubiKey hardware key by Yubico as a 2FA method to log in to your WordPress website](https://melapress.com/support/kb/wp-2fa-hardware-key/).
* **Plugin & functionality improvements**
* Bumped up the minimum supported PHP version from 7.2 to 7.3.
* Updated a number of strings in the settings + improved help text.
* The names of debug log file in uploads directory are now randomized.
* Updated the default text in different sections of the wizard to simplify things and improve UX.
* Adjusted the order in which the 2FA methods are listed.
* Updated the features' page in the plugin - added the new features etc.
* Updated all UTM parameters in the plugin's URLs and links.
* **Bug fixes**
* Fixed: PHP fatal error in class-email-wizard-steps.php in some edge cases.
* Fixed: Apostrophe character shows up as ASCII in email subject.
* Fixed: Error with importing plugin's settings from one website to another in some edge cases.
* Fixed: The grace period expiration setting did not have a default value / setting.
* Removed reference to Premium backup methods in the free edition's wizard.
* Fixed: Redirecting to frontend 2FA page without permalinks set up does not work.
* Fixed: Some user profile 2FA buttons were not functioning properly when used on mobile.
* Fixed: Data was not always / all deleted when the setting "Delete data upon uninstall" was enabled.
Refer to the complete [plugin changelog](https://melapress.com/support/kb/wp-2fa-plugin-changelog/?utm_source=wordpress.org&utm_medium=referral&utm_campaign=WP2FA&utm_content=plugin+repos+description) for more detailed information about what was new, improved and fixed in previous version updates of WP 2FA.
index.php 0000644 00000000046 15075513060 0006366 0 ustar 00 <?php
/**
* Nothing to see here.
*/
languages/wp-2fa-de_DE.mo 0000644 00000227146 15075513060 0011117 0 ustar 00 �� . � � �"