Admin
11 months ago
App
11 months ago
Authenticator
11 months ago
Shortcodes
11 months ago
Utils
11 months ago
bacon
11 months ago
dasprid
11 months ago
class-email-template.php
11 months ago
class-wp2fa.php
11 months ago
index.php
11 months ago
class-wp2fa.php
1316 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Main plugin class. |
| 4 | * |
| 5 | * @package wp2fa |
| 6 | * @copyright 2025 Melapress |
| 7 | * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 |
| 8 | * @link https://wordpress.org/plugins/wp-2fa/ |
| 9 | */ |
| 10 | |
| 11 | namespace WP2FA; |
| 12 | |
| 13 | use WP2FA\Methods\TOTP; |
| 14 | use WP2FA\Utils\White_Label; |
| 15 | use WP2FA\Admin\Setup_Wizard; |
| 16 | use WP2FA\Admin\User_Listing; |
| 17 | use WP2FA\Admin\User_Notices; |
| 18 | use WP2FA\Admin\User_Profile; |
| 19 | use WP2FA\Admin\FlyOut\FlyOut; |
| 20 | use WP2FA\Admin\Settings_Page; |
| 21 | use WP2FA\Authenticator\Login; |
| 22 | use WP2FA\Utils\Request_Utils; |
| 23 | use WP2FA\Methods\Backup_Codes; |
| 24 | use WP2FA\Utils\Settings_Utils; |
| 25 | use WP2FA\Admin\User_Registered; |
| 26 | use WP2FA\Shortcodes\Shortcodes; |
| 27 | use WP2FA\Utils\Date_Time_Utils; |
| 28 | use WP2FA\Admin\Premium_Features; |
| 29 | use WP2FA\Authenticator\Open_SSL; |
| 30 | use WP2FA\Admin\Helpers\WP_Helper; |
| 31 | use WP2FA\Freemius\User_Licensing; |
| 32 | use WP2FA\Admin\Views\Re_Login_2FA; |
| 33 | use WP2FA\Admin\Controllers\Methods; |
| 34 | use WP2FA\Admin\Helpers\Ajax_Helper; |
| 35 | use WP2FA\Admin\Helpers\File_Writer; |
| 36 | use WP2FA\Admin\Helpers\User_Helper; |
| 37 | use WP2FA\Admin\Controllers\Settings; |
| 38 | use WP2FA\Admin\Controllers\Endpoints; |
| 39 | use WP2FA\Admin\Plugin_Updated_Notice; |
| 40 | use WP2FA\Admin\Helpers\Classes_Helper; |
| 41 | use WP2FA\Admin\Helpers\Methods_Helper; |
| 42 | use WP2FA\Authenticator\Reset_Password; |
| 43 | use WP2FA\Admin\Views\Password_Reset_2FA; |
| 44 | use WP2FA\Admin\Views\Grace_Period_Notifications; |
| 45 | |
| 46 | if ( ! class_exists( '\WP2FA\WP2FA' ) ) { |
| 47 | /** |
| 48 | * Main WP2FA Class. |
| 49 | */ |
| 50 | class WP2FA { |
| 51 | |
| 52 | /** |
| 53 | * Holds the global plugin secret key |
| 54 | * |
| 55 | * @var string |
| 56 | * |
| 57 | * @since 2.0.0 |
| 58 | */ |
| 59 | private static $secret_key = null; |
| 60 | |
| 61 | /** |
| 62 | * Local static cache for plugins settings. |
| 63 | * |
| 64 | * @var array |
| 65 | * |
| 66 | * @since 2.0.0 |
| 67 | */ |
| 68 | private static $plugin_settings = array(); |
| 69 | |
| 70 | /** |
| 71 | * Local static cache for plugins type. |
| 72 | * |
| 73 | * @var string |
| 74 | * |
| 75 | * @since 3.0.0 |
| 76 | */ |
| 77 | private static $plugin_type = null; |
| 78 | |
| 79 | /** |
| 80 | * Local static cache for plugins settings. |
| 81 | * |
| 82 | * @var array |
| 83 | * |
| 84 | * @since 2.8.0 |
| 85 | */ |
| 86 | private static $default_settings = array(); |
| 87 | |
| 88 | /** |
| 89 | * Local static cache for email template settings. |
| 90 | * |
| 91 | * @var array |
| 92 | */ |
| 93 | protected static $wp_2fa_email_templates; |
| 94 | |
| 95 | /** |
| 96 | * Array with all the plugin default settings. |
| 97 | * |
| 98 | * @return array |
| 99 | * |
| 100 | * @since 2.2.0 |
| 101 | */ |
| 102 | public static function get_default_settings() { |
| 103 | if ( empty( self::$default_settings ) ) { |
| 104 | self::$default_settings = array( |
| 105 | 'enforcement-policy' => 'do-not-enforce', |
| 106 | 'excluded_users' => array(), |
| 107 | 'excluded_roles' => array(), |
| 108 | 'enforced_users' => array(), |
| 109 | 'enforced_roles' => array(), |
| 110 | 'grace-period' => 3, |
| 111 | 'grace-period-denominator' => 'days', |
| 112 | 'enable_destroy_session' => '', |
| 113 | 'limit_access' => '', |
| 114 | 'enable_rest' => false, |
| 115 | 'brute_force_disable' => '', |
| 116 | '2fa_settings_last_updated_by' => '', |
| 117 | '2fa_main_user' => '', |
| 118 | 'grace-period-expiry-time' => '', |
| 119 | 'plugin_version' => WP_2FA_VERSION, |
| 120 | 'delete_data_upon_uninstall' => '', |
| 121 | 'excluded_sites' => array(), |
| 122 | 'included_sites' => array(), |
| 123 | 'create-custom-user-page' => 'no', |
| 124 | 'redirect-user-custom-page' => '', |
| 125 | 'redirect-user-custom-page-global' => '', |
| 126 | 'custom-user-page-url' => '', |
| 127 | 'custom-user-page-id' => '', |
| 128 | 'hide_remove_button' => '', |
| 129 | 'separate-multisite-page-url' => '', |
| 130 | 'grace-policy' => 'use-grace-period', |
| 131 | 'superadmins-role-add' => 'no', |
| 132 | 'superadmins-role-exclude' => 'no', |
| 133 | 'method_invalid_setting' => 'login_block', |
| 134 | ); |
| 135 | /** |
| 136 | * Gives the ability to filter the default settings array of the plugin |
| 137 | * |
| 138 | * @param array $settings - The array with all the default settings. |
| 139 | * |
| 140 | * @since 2.0.0 |
| 141 | */ |
| 142 | self::$default_settings = \apply_filters( WP_2FA_PREFIX . 'default_settings', self::$default_settings ); |
| 143 | } |
| 144 | |
| 145 | return self::$default_settings; |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Inits the plugin related classes and settings |
| 150 | * |
| 151 | * @return void |
| 152 | * |
| 153 | * @since 2.6.0 |
| 154 | */ |
| 155 | public static function init() { |
| 156 | |
| 157 | \add_filter( |
| 158 | 'aadvana_trigger_error', |
| 159 | function ( $trigger_error, $function_name, $errstr ) { |
| 160 | if ( '_load_textdomain_just_in_time' === $function_name && strpos( $errstr, '<code>' . WP_2FA_TEXTDOMAIN ) !== false ) { |
| 161 | $trigger_error = false; |
| 162 | } |
| 163 | |
| 164 | return $trigger_error; |
| 165 | }, |
| 166 | 10, |
| 167 | 3 |
| 168 | ); |
| 169 | |
| 170 | Methods_Helper::init(); |
| 171 | |
| 172 | |
| 173 | self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() ); |
| 174 | self::$plugin_settings[ WP_2FA_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_SETTINGS_NAME, array() ); |
| 175 | self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = ( ! empty( Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() ) ) ) ? Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() ) : White_Label::get_default_settings(); |
| 176 | |
| 177 | self::$wp_2fa_email_templates = Settings_Utils::get_option( WP_2FA_EMAIL_SETTINGS_NAME ); |
| 178 | |
| 179 | White_Label::init(); |
| 180 | |
| 181 | /** 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) */ |
| 182 | if ( Settings_Utils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) { |
| 183 | |
| 184 | if ( ! Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) { |
| 185 | self::update_plugin_settings( self::get_default_settings() ); |
| 186 | } |
| 187 | |
| 188 | // Set a flag so we know we have default values present, not custom. |
| 189 | Settings_Utils::update_option( 'default_settings_applied', true ); |
| 190 | Settings_Utils::delete_option( 'wizard_not_finished' ); |
| 191 | } |
| 192 | |
| 193 | |
| 194 | WP_Helper::init(); |
| 195 | |
| 196 | // Bootstrap. |
| 197 | Core\setup(); |
| 198 | |
| 199 | if ( \is_admin() && ! \wp_doing_ajax() ) { |
| 200 | User_Listing::init(); |
| 201 | // Hide all unrelated to the plugin notices on the plugin admin pages. |
| 202 | \add_action( 'admin_print_scripts', array( WP_Helper::class, 'hide_unrelated_notices' ) ); |
| 203 | } |
| 204 | |
| 205 | Grace_Period_Notifications::init(); |
| 206 | Password_Reset_2FA::init(); |
| 207 | Re_Login_2FA::init(); |
| 208 | |
| 209 | Shortcodes::init(); |
| 210 | \add_action( 'after_setup_theme', array( User_Notices::class, 'init' ), 10 ); |
| 211 | \add_action( 'after_setup_theme', array( FlyOut::class, 'init' ), 10 ); |
| 212 | Plugin_Updated_Notice::init(); |
| 213 | |
| 214 | self::add_actions(); |
| 215 | |
| 216 | /** |
| 217 | * Enables the API endpoints for the plugin. |
| 218 | * |
| 219 | * @since 2.9.1 |
| 220 | */ |
| 221 | if ( Settings_Utils::string_to_bool( self::get_wp2fa_general_setting( 'enable_rest' ) ) ) { |
| 222 | Endpoints::init(); |
| 223 | } |
| 224 | |
| 225 | // Inits all the additional free app extensions. |
| 226 | $free_extensions = Classes_Helper::get_classes_by_namespace( 'WP2FA\\App\\' ); |
| 227 | |
| 228 | foreach ( $free_extensions as $extension ) { |
| 229 | if ( method_exists( $extension, 'init' ) ) { |
| 230 | call_user_func_array( array( $extension, 'init' ), array() ); |
| 231 | } |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | /** |
| 236 | * Inits all the plugin hooks |
| 237 | * |
| 238 | * @return void |
| 239 | * |
| 240 | * @since 2.6.0 |
| 241 | */ |
| 242 | public static function add_actions() { |
| 243 | // Plugin redirect on activation, only if we have no settings currently saved. |
| 244 | 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 ) ) { |
| 245 | \add_action( 'admin_init', array( __CLASS__, 'setup_redirect' ), 10 ); |
| 246 | } elseif ( ! \is_array( Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) ) { |
| 247 | Settings_Utils::delete_option( WP_2FA_POLICY_SETTINGS_NAME ); |
| 248 | self::update_plugin_settings( self::get_default_settings() ); |
| 249 | } |
| 250 | if ( Settings_Utils::get_option( 'wizard_not_finished' ) && function_exists( 'wp_get_current_user' ) && \current_user_can( 'manage_options' ) ) { |
| 251 | \add_action( 'admin_init', array( Setup_Wizard::class, 'setup_page' ), 10 ); |
| 252 | } |
| 253 | if ( Settings_Utils::get_option( 'wizard_not_finished' ) && function_exists( 'wp_get_current_user' ) && \current_user_can( 'manage_options' ) ) { |
| 254 | \add_action( 'admin_init', array( Setup_Wizard::class, 'setup_page' ), 10 ); |
| 255 | } |
| 256 | |
| 257 | // SettingsPage. |
| 258 | if ( WP_Helper::is_multisite() ) { |
| 259 | \add_action( 'network_admin_menu', array( Settings_Page::class, 'create_settings_admin_menu_multisite' ) ); |
| 260 | \add_action( 'network_admin_edit_update_wp2fa_network_options', array( Settings_Page::class, 'update_wp2fa_network_options' ) ); |
| 261 | \add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( Settings_Page::class, 'update_wp2fa_network_email_options' ) ); |
| 262 | \add_action( 'network_admin_notices', array( Settings_Page::class, 'settings_saved_network_admin_notice' ) ); |
| 263 | \add_action( 'network_admin_notices', array( __CLASS__, 'wp_not_writable' ) ); |
| 264 | } else { |
| 265 | \add_action( 'admin_menu', array( Settings_Page::class, 'create_settings_admin_menu' ) ); |
| 266 | \add_action( 'admin_notices', array( Settings_Page::class, 'settings_saved_admin_notice' ) ); |
| 267 | \add_action( 'admin_notices', array( __CLASS__, 'wp_not_writable' ) ); |
| 268 | } |
| 269 | \add_action( 'wp_ajax_wp2fa_dismiss_notice_mail_domain', array( Settings_Page::class, 'dismiss_notice_mail_domain' ) ); |
| 270 | |
| 271 | \add_action( 'wp_ajax_set_salt_key', array( Ajax_Helper::class, 'set_salt_key' ) ); |
| 272 | \add_action( 'wp_ajax_unset_salt_key', array( Ajax_Helper::class, 'unset_salt_key' ) ); |
| 273 | |
| 274 | \add_action( 'wp_ajax_wp_2fa_get_all_users', array( Ajax_Helper::class, 'get_all_users' ) ); |
| 275 | \add_action( 'wp_ajax_wp_2fa_get_all_roles', array( Ajax_Helper::class, 'get_ajax_user_roles' ) ); |
| 276 | \add_action( 'wp_ajax_wp_2fa_get_all_network_sites', array( Ajax_Helper::class, 'get_all_network_sites' ) ); |
| 277 | \add_action( 'wp_ajax_unlock_account', array( Ajax_Helper::class, 'unlock_account' ), 10, 1 ); |
| 278 | \add_action( 'admin_action_unlock_account', array( Ajax_Helper::class, 'unlock_account' ), 10, 1 ); |
| 279 | \add_action( 'admin_action_remove_user_2fa', array( Ajax_Helper::class, 'remove_user_2fa' ), 10, 1 ); |
| 280 | \add_action( 'wp_ajax_remove_user_2fa', array( Ajax_Helper::class, 'remove_user_2fa' ), 10, 1 ); |
| 281 | \add_action( 'admin_menu', array( Settings_Page::class, 'hide_settings' ), 999 ); |
| 282 | \add_action( 'plugin_action_links_' . WP_2FA_BASE, array( Settings_Page::class, 'add_plugin_action_links' ) ); |
| 283 | \add_filter( 'display_post_states', array( Settings_Page::class, 'add_display_post_states' ), 10, 2 ); |
| 284 | \add_action( 'wp_ajax_send_authentication_setup_email', array( Setup_Wizard::class, 'send_authentication_setup_email' ) ); |
| 285 | \add_action( 'wp_ajax_send_backup_codes_email', array( Backup_Codes::class, 'send_backup_codes_email' ) ); |
| 286 | \add_action( 'wp_ajax_regenerate_authentication_key', array( TOTP::class, 'regenerate_authentication_key' ) ); |
| 287 | |
| 288 | // User_Notices. |
| 289 | \add_action( 'wp_ajax_dismiss_nag', array( User_Notices::class, 'dismiss_nag' ) ); |
| 290 | \add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( User_Notices::class, 'dismiss_nag' ) ); |
| 291 | \add_action( 'wp_logout', array( User_Notices::class, 'reset_nag' ), 10, 1 ); |
| 292 | |
| 293 | // User_Profile. |
| 294 | global $pagenow; |
| 295 | if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) { |
| 296 | \add_action( 'show_user_profile', array( User_Profile::class, 'inline_2fa_profile_form' ) ); |
| 297 | \add_action( 'edit_user_profile', array( User_Profile::class, 'inline_2fa_profile_form' ) ); |
| 298 | if ( WP_Helper::is_multisite() ) { |
| 299 | \add_action( 'personal_options_update', array( User_Profile::class, 'save_user_2fa_options' ) ); |
| 300 | } |
| 301 | } |
| 302 | \add_filter( 'user_row_actions', array( User_Profile::class, 'user_2fa_row_actions' ), 10, 2 ); |
| 303 | if ( WP_Helper::is_multisite() ) { |
| 304 | \add_filter( 'ms_user_row_actions', array( User_Profile::class, 'user_2fa_row_actions' ), 10, 2 ); |
| 305 | } |
| 306 | \add_action( 'wp_ajax_validate_authcode_via_ajax', array( User_Profile::class, 'validate_authcode_via_ajax' ) ); |
| 307 | \add_action( 'wp_ajax_wp2fa_test_email', array( Ajax_Helper::class, 'handle_send_test_email_ajax' ) ); |
| 308 | |
| 309 | // Login. |
| 310 | \add_action( 'wp_login', array( Login::class, 'wp_login' ), 20, 2 ); |
| 311 | \add_action( 'wp_loaded', array( Login::class, 'login_form_validate_2fa' ) ); |
| 312 | \add_action( 'login_form_validate_2fa', array( Login::class, 'login_form_validate_2fa' ) ); |
| 313 | \add_action( 'login_form_backup_2fa', array( Login::class, 'backup_2fa' ) ); |
| 314 | \add_action( 'login_enqueue_scripts', array( Login::class, 'dequeue_style' ), PHP_INT_MAX ); |
| 315 | |
| 316 | // Reset password. |
| 317 | \add_action( 'lostpassword_post', array( Reset_Password::class, 'lostpassword_post' ), 20, 2 ); |
| 318 | \add_action( 'login_form_lostpassword', array( Reset_Password::class, 'login_form_validate_2fa' ), 20 ); |
| 319 | // \add_action( 'wp_loaded', array( Reset_Password::class, 'login_form_validate_2fa' ) );. |
| 320 | |
| 321 | /** |
| 322 | * Keep track of all the user sessions for which we need to invalidate the |
| 323 | * authentication cookies set during the initial password check. |
| 324 | */ |
| 325 | \add_action( 'set_auth_cookie', array( Login::class, 'collect_auth_cookie_tokens' ) ); |
| 326 | \add_action( 'set_logged_in_cookie', array( Login::class, 'collect_auth_cookie_tokens' ) ); |
| 327 | |
| 328 | // Run only after the core wp_authenticate_username_password() check. |
| 329 | \add_filter( 'authenticate', array( Login::class, 'filter_authenticate' ), 50 ); |
| 330 | \add_filter( 'wp_authenticate_user', array( Login::class, 'run_authentication_check' ), 10, 2 ); |
| 331 | |
| 332 | // User Register. |
| 333 | \add_action( 'set_user_role', array( User_Registered::class, 'check_user_upon_role_change' ), 10, 3 ); |
| 334 | |
| 335 | // Block users from admin if needed. |
| 336 | $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp'; |
| 337 | \add_action( $user_block_hook, array( __CLASS__, 'block_unconfigured_users_from_admin' ), 10 ); |
| 338 | |
| 339 | // Help & Contact Us. |
| 340 | \add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( '\WP2FA\Admin\Help_Contact_Us', 'add_extra_menu_item' ) ); |
| 341 | |
| 342 | // phpcs:disable |
| 343 | /* @free:start */ |
| 344 | // phpcs:enable |
| 345 | // Premium Features. |
| 346 | \add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( Premium_Features::class, 'add_extra_menu_item' ) ); |
| 347 | \add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( Premium_Features::class, 'add_settings_banner' ) ); |
| 348 | \add_action( 'admin_footer', array( Premium_Features::class, 'pricing_new_tab_js' ) ); |
| 349 | // phpcs:disable |
| 350 | /* @free:end */ |
| 351 | // phpcs:enable |
| 352 | |
| 353 | \add_action( 'admin_footer', array( User_Profile::class, 'dismiss_nag_notice' ) ); |
| 354 | |
| 355 | \add_action( WP_2FA_PREFIX . 'user_authenticated', array( __CLASS__, 'clear_user_after_login' ), 10, 1 ); |
| 356 | |
| 357 | \add_filter( 'mepr-auto-login', array( Login::class, 'mepr_login' ) ); |
| 358 | } |
| 359 | |
| 360 | /** |
| 361 | * Add actions specific to the wizard. |
| 362 | * |
| 363 | * @since 2.0.0 |
| 364 | */ |
| 365 | public static function add_wizard_actions() { |
| 366 | if ( function_exists( 'wp_get_current_user' ) && \current_user_can( 'read' ) ) { |
| 367 | \add_action( 'admin_init', array( '\WP2FA\Admin\Setup_Wizard', 'setup_page' ), 10 ); |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | /** |
| 372 | * Redirect user to 1st time setup. |
| 373 | * |
| 374 | * @since 2.0.0 |
| 375 | */ |
| 376 | public static function setup_redirect() { |
| 377 | |
| 378 | // Bail early before the redirect if the user can't manage options. |
| 379 | if ( wp_doing_ajax() && ! \current_user_can( 'manage_options' ) ) { |
| 380 | return; |
| 381 | } |
| 382 | |
| 383 | $registered_and_active = 'yes'; |
| 384 | if ( function_exists( 'wp2fa_freemius' ) ) { |
| 385 | $registered_and_active = wp2fa_freemius()->is_registered() && wp2fa_freemius()->has_active_valid_license() ? 'yes' : 'no'; |
| 386 | } |
| 387 | |
| 388 | if ( Settings_Utils::get_option( 'redirect_on_activate', false ) && 'yes' === $registered_and_active ) { |
| 389 | // Delete redirect option. |
| 390 | Settings_Utils::delete_option( 'redirect_on_activate' ); |
| 391 | |
| 392 | Settings_Utils::update_option( 'wizard_not_finished', true ); |
| 393 | |
| 394 | $redirect = \add_query_arg( |
| 395 | array( |
| 396 | 'page' => 'wp-2fa-setup', |
| 397 | 'is_initial_setup' => 'true', |
| 398 | ), |
| 399 | \network_admin_url( 'user-edit.php' ) |
| 400 | ); |
| 401 | |
| 402 | \wp_safe_redirect( $redirect ); |
| 403 | exit(); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | /** |
| 408 | * Util function to grab settings or apply defaults if no settings are saved into the db. |
| 409 | * |
| 410 | * @param string $setting_name Settings to grab value of. |
| 411 | * @param boolean $get_default_on_empty return default setting value if current one is empty. |
| 412 | * @param boolean $get_default_value return default value setting (ignore the stored ones). |
| 413 | * @param string $role - The name of the user role. |
| 414 | * |
| 415 | * @return mixed Settings value or default value. |
| 416 | * |
| 417 | * @since 2.0.0 |
| 418 | */ |
| 419 | public static function get_wp2fa_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) { |
| 420 | $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role; |
| 421 | return self::get_wp2fa_setting_generic( WP_2FA_POLICY_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value, $role ); |
| 422 | } |
| 423 | |
| 424 | /** |
| 425 | * Util function to grab settings or apply defaults if no settings are saved into the db. |
| 426 | * |
| 427 | * @param string $setting_name Settings to grab value of. |
| 428 | * @param boolean $get_default_on_empty return default setting value if current one is empty. |
| 429 | * @param boolean $get_default_value return default value setting (ignore the stored ones). |
| 430 | * |
| 431 | * @return mixed Settings value or default value. |
| 432 | * |
| 433 | * @since 3.0.0 |
| 434 | */ |
| 435 | public static function get_wp2fa_general_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) { |
| 436 | |
| 437 | return self::get_wp2fa_setting_generic( WP_2FA_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value ); |
| 438 | } |
| 439 | |
| 440 | /** |
| 441 | * Util function to grab white label settings or apply defaults if no settings are saved into the db. |
| 442 | * |
| 443 | * @param string $setting_name Settings to grab value of. |
| 444 | * @param boolean $get_default_on_empty return default setting value if current one is empty. |
| 445 | * @param boolean $get_default_value return default value setting (ignore the stored ones). |
| 446 | * |
| 447 | * @return string Settings value or default value. |
| 448 | * |
| 449 | * @since 2.0.0 |
| 450 | */ |
| 451 | public static function get_wp2fa_white_label_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) { |
| 452 | |
| 453 | return (string) self::get_wp2fa_setting_generic( WP_2FA_WHITE_LABEL_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value ); |
| 454 | } |
| 455 | |
| 456 | /** |
| 457 | * Generic method for extracting settings from the plugin |
| 458 | * |
| 459 | * @param string $wp_2fa_setting - The name of the settings type. |
| 460 | * @param string $setting_name - The name of the setting to extract. |
| 461 | * @param boolean $get_default_on_empty - Should we use default value on empty. |
| 462 | * @param boolean $get_default_value - Extract default value. |
| 463 | * @param string $role - The name of the user role. |
| 464 | * |
| 465 | * @return mixed |
| 466 | * |
| 467 | * @since 2.0.0 |
| 468 | */ |
| 469 | 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' ) { |
| 470 | |
| 471 | $default_settings = self::get_default_settings(); |
| 472 | $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role; |
| 473 | |
| 474 | if ( true === $get_default_value ) { |
| 475 | if ( isset( $default_settings[ $setting_name ] ) ) { |
| 476 | return $default_settings[ $setting_name ]; |
| 477 | } |
| 478 | |
| 479 | return false; |
| 480 | } |
| 481 | |
| 482 | $apply_defaults = false; |
| 483 | |
| 484 | $wp2fa_setting = self::$plugin_settings[ $wp_2fa_setting ]; |
| 485 | |
| 486 | // If we have no setting name, return them all. |
| 487 | if ( empty( $setting_name ) ) { |
| 488 | return $wp2fa_setting; |
| 489 | } |
| 490 | |
| 491 | // First lets check if any options have been saved. |
| 492 | if ( empty( $wp2fa_setting ) || ! isset( $wp2fa_setting ) ) { |
| 493 | $apply_defaults = true; |
| 494 | } |
| 495 | |
| 496 | if ( $apply_defaults ) { |
| 497 | return isset( $default_settings[ $setting_name ] ) ? $default_settings[ $setting_name ] : false; |
| 498 | } elseif ( ! isset( $wp2fa_setting[ $setting_name ] ) ) { |
| 499 | if ( true === $get_default_on_empty ) { |
| 500 | if ( isset( $default_settings[ $setting_name ] ) ) { |
| 501 | return $default_settings[ $setting_name ]; |
| 502 | } elseif ( isset( $default_settings[ $wp_2fa_setting ][ $setting_name ] ) ) { |
| 503 | return $default_settings[ $wp_2fa_setting ][ $setting_name ]; |
| 504 | } |
| 505 | } |
| 506 | return false; |
| 507 | } elseif ( WP_2FA_POLICY_SETTINGS_NAME === $wp_2fa_setting ) { |
| 508 | |
| 509 | /** |
| 510 | * Extensions could change the extracted value, based on custom / different / specific for role settings. |
| 511 | * |
| 512 | * @param mixed - Value of the setting. |
| 513 | * @param string - The name of the setting. |
| 514 | * @param string - The role name. |
| 515 | * |
| 516 | * @since 2.0.0 |
| 517 | */ |
| 518 | return \apply_filters( WP_2FA_PREFIX . 'setting_generic', $wp2fa_setting[ $setting_name ], $setting_name, $role ); |
| 519 | } else { |
| 520 | return $wp2fa_setting[ $setting_name ]; |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | /** |
| 525 | * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db. |
| 526 | * |
| 527 | * @param string $setting_name Settings to grab value of. |
| 528 | * |
| 529 | * @since 2.0.0 |
| 530 | */ |
| 531 | public static function get_wp2fa_email_templates( $setting_name = '' ) { |
| 532 | |
| 533 | // If we have no setting name, return what ever is saved. |
| 534 | if ( empty( $setting_name ) ) { |
| 535 | return self::$wp_2fa_email_templates; |
| 536 | } |
| 537 | |
| 538 | // If we have a saved setting, return it. |
| 539 | if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) { |
| 540 | return self::$wp_2fa_email_templates[ $setting_name ]; |
| 541 | } |
| 542 | |
| 543 | // Create Login Code Message. |
| 544 | $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' ); |
| 545 | |
| 546 | $login_code_body = '<p>' . \esc_html__( 'Hello {user_display_name},', 'wp-2fa' ) . '</p>'; |
| 547 | $login_code_body .= '<p>' . \esc_html__( 'You are trying to log in to {site_name} using the username {user_login_name}. To complete your login, please enter the following one-time 2FA code:', 'wp-2fa' ) . '</p>'; |
| 548 | $login_code_body .= '<p>' . \esc_html__( '{login_code}', 'wp-2fa' ) . '</p>'; |
| 549 | $login_code_body .= '<p>' . \esc_html__( 'Enter this code on the login page to finish the authentication process and access your account.', 'wp-2fa' ) . '</p>'; |
| 550 | $login_code_body .= '<p>' . \esc_html__( 'If you encounter any issues logging in, feel free to contact us at {admin_email}.', 'wp-2fa' ) . '</p>'; |
| 551 | $login_code_body .= '<p>' . \esc_html__( |
| 552 | 'Kind regards, |
| 553 | The {site_name} Team', |
| 554 | 'wp-2fa' |
| 555 | ) . '</p>'; |
| 556 | |
| 557 | // $login_code_body .= '<p>' . sprintf( |
| 558 | // // translators: The login code provided from the plugin. |
| 559 | // \esc_html__( 'Enter %1$1s to log in.', 'wp-2fa' ), |
| 560 | // '<strong>{login_code}</strong>' |
| 561 | // ); |
| 562 | // $login_code_body .= '</p>'; |
| 563 | // $login_code_body .= '<p>' . \esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>'; |
| 564 | // $login_code_body .= '<p>' . \esc_html__( 'Email sent by', 'wp-2fa' ); |
| 565 | // $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>'; |
| 566 | // $login_code_body .= '</p>'; |
| 567 | |
| 568 | // Create Reset PW Code Message. |
| 569 | $reset_password_code_subject = __( '2FA code for password reset', 'wp-2fa' ); |
| 570 | |
| 571 | $reset_password_code_body = '<p>' . \esc_html__( 'Hello,', 'wp-2fa' ) . '</p>'; |
| 572 | |
| 573 | $reset_password_code_body = '<p>' . sprintf( |
| 574 | // translators: The login code provided from the plugin. |
| 575 | \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' ), |
| 576 | '{user_ip_address}', |
| 577 | '{user_login_name}', |
| 578 | '{site_url}' |
| 579 | ); |
| 580 | |
| 581 | $reset_password_code_body .= '<p><strong>{login_code}</strong></p>'; |
| 582 | |
| 583 | $reset_password_code_body .= '</p>'; |
| 584 | $reset_password_code_body .= '<p>' . \esc_html__( 'If this was not you, ignore this email and contact your website administrator.', 'wp-2fa' ) . '</p>'; |
| 585 | |
| 586 | $login_code_setup_body = '<p>' . sprintf( |
| 587 | // translators: The login code provided from the plugin. |
| 588 | \esc_html__( 'Please enter this code to confirm 2FA setup: %1$1s', 'wp-2fa' ), |
| 589 | '<strong>{login_code}</strong>' |
| 590 | ); |
| 591 | $login_code_setup_body .= '</p>'; |
| 592 | $login_code_setup_body .= '<p>' . \esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>'; |
| 593 | $login_code_setup_body .= '<p>' . \esc_html__( 'Email sent by', 'wp-2fa' ); |
| 594 | $login_code_setup_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=melapress_wp_2fa_plugin_link_2" target="_blank">' . \esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>'; |
| 595 | $login_code_setup_body .= '</p>'; |
| 596 | |
| 597 | // Create User Locked Message. |
| 598 | $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' ); |
| 599 | |
| 600 | $user_locked_body = '<p>' . \esc_html__( 'Hello.', 'wp-2fa' ) . '</p>'; |
| 601 | $user_locked_body .= '<p>' . sprintf( |
| 602 | // translators: %1s - the name of the user |
| 603 | // translators: %2s - the name of the site. |
| 604 | \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' ), |
| 605 | '{user_login_name}', |
| 606 | '{site_name}' |
| 607 | ); |
| 608 | $user_locked_body .= '</p>'; |
| 609 | $user_locked_body .= '<p>' . \esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>'; |
| 610 | $user_locked_body .= '<p>' . \esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>'; |
| 611 | $user_locked_body .= '<p>' . \esc_html__( 'Email sent by', 'wp-2fa' ); |
| 612 | $user_locked_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=melapress_wp_2fa_plugin_link_3" target="_blank">' . \esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>'; |
| 613 | $user_locked_body .= '</p>'; |
| 614 | |
| 615 | // Create User unlocked Message. |
| 616 | $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' ); |
| 617 | $user_unlocked_body = ''; |
| 618 | |
| 619 | $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>'; |
| 620 | |
| 621 | if ( ! empty( self::get_wp2fa_setting( 'custom-user-page-id' ) ) ) { |
| 622 | $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>'; |
| 623 | } |
| 624 | |
| 625 | $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=wp2fa&utm_campaign=melapress_wp_2fa_plugin_link_4" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>'; |
| 626 | |
| 627 | // Create User backup codes Message. |
| 628 | $user_backup_codes_subject = __( '2FA backup codes for user {user_login_name} on {site_name}', 'wp-2fa' ); |
| 629 | $user_backup_codes_body = ''; |
| 630 | |
| 631 | $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>'; |
| 632 | |
| 633 | $user_backup_codes_body .= '{backup_codes}'; |
| 634 | |
| 635 | $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=wp2fa&utm_campaign=melapress_wp_2fa_plugin_link_5" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>'; |
| 636 | |
| 637 | // Array of defaults, now we have things setup above. |
| 638 | $default_settings = array( |
| 639 | 'email_from_setting' => 'use-defaults', |
| 640 | 'custom_from_email_address' => '', |
| 641 | 'custom_from_display_name' => '', |
| 642 | 'login_code_email_subject' => $login_code_subject, |
| 643 | 'login_code_email_body' => $login_code_body, |
| 644 | 'reset_password_code_email_subject' => $reset_password_code_subject, |
| 645 | 'reset_password_code_email_body' => $reset_password_code_body, |
| 646 | 'login_code_setup_email_subject' => $login_code_subject, |
| 647 | 'login_code_setup_email_body' => $login_code_setup_body, |
| 648 | 'user_account_locked_email_subject' => $user_locked_subject, |
| 649 | 'user_account_locked_email_body' => $user_locked_body, |
| 650 | 'user_account_unlocked_email_subject' => $user_unlocked_subject, |
| 651 | 'user_account_unlocked_email_body' => $user_unlocked_body, |
| 652 | 'user_backup_codes_email_subject' => $user_backup_codes_subject, |
| 653 | 'user_backup_codes_email_body' => $user_backup_codes_body, |
| 654 | 'send_account_locked_email' => 'enable_account_locked_email', |
| 655 | 'send_account_unlocked_email' => 'enable_account_unlocked_email', |
| 656 | 'send_login_code_email' => 'enable_send_login_code_email', |
| 657 | 'send_reset_password_code_email' => 'enable_send_reset_password_code_email', |
| 658 | ); |
| 659 | |
| 660 | /** |
| 661 | * Allows 3rd party providers to their own settings for the mail templates. |
| 662 | * |
| 663 | * @param array $default_settings - Array with the default settings. |
| 664 | * |
| 665 | * @since 2.0.0 |
| 666 | */ |
| 667 | $default_settings = \apply_filters( WP_2FA_PREFIX . 'mail_default_settings', $default_settings ); |
| 668 | |
| 669 | return $default_settings[ $setting_name ]; |
| 670 | } |
| 671 | |
| 672 | /** |
| 673 | * Util which we use to replace our {strings} with actual, useful stuff. |
| 674 | * |
| 675 | * @param string $input Text we are working on. |
| 676 | * @param int|string $user_id User id, if its needed. |
| 677 | * @param string $token Login code, if its needed.. |
| 678 | * @param string $override_grace_period - Value to override grace period with. |
| 679 | * |
| 680 | * @return string The output, with all the {strings} swapped out. |
| 681 | * |
| 682 | * @since 2.0.0 |
| 683 | */ |
| 684 | public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) { |
| 685 | |
| 686 | if ( empty( $GLOBALS['wp_rewrite'] ) ) { |
| 687 | $GLOBALS['wp_rewrite'] = new \WP_Rewrite(); // phpcs:ignore -- WordPress.WP.GlobalVariablesOverride.Prohibited |
| 688 | } |
| 689 | |
| 690 | $token = trim( (string) $token ); |
| 691 | |
| 692 | // Gather grace period. |
| 693 | $grace_period_string = ''; |
| 694 | if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) { |
| 695 | $grace_period_string = sanitize_text_field( $override_grace_period ); |
| 696 | } else { |
| 697 | $grace_policy = self::get_wp2fa_setting( 'grace-policy' ); |
| 698 | $grace_period_string = Date_Time_Utils::format_grace_period_expiration_string( $grace_policy ); |
| 699 | } |
| 700 | |
| 701 | // Setup user data. |
| 702 | if ( isset( $user_id ) && ! empty( $user_id ) ) { |
| 703 | $user = get_userdata( intval( $user_id ) ); |
| 704 | } else { |
| 705 | $user = wp_get_current_user(); |
| 706 | } |
| 707 | |
| 708 | // Setup token. |
| 709 | if ( isset( $token ) && ! empty( $token ) ) { |
| 710 | $login_code = $token; |
| 711 | } else { |
| 712 | $login_code = ''; |
| 713 | } |
| 714 | |
| 715 | $new_page_id = Settings_Utils::get_setting_role( User_Helper::get_user_role( $user ), 'custom-user-page-id' ); |
| 716 | if ( ! empty( $new_page_id ) ) { |
| 717 | $new_page_permalink = esc_url( \get_permalink( intval( $new_page_id ) ) ); |
| 718 | } else { |
| 719 | $new_page_id = Settings::get_custom_settings_page_id( '', $user ); |
| 720 | if ( ! empty( $new_page_id ) ) { |
| 721 | $new_page_permalink = esc_url( \get_permalink( intval( $new_page_id ) ) ); |
| 722 | } else { |
| 723 | $new_page_permalink = ''; |
| 724 | } |
| 725 | } |
| 726 | |
| 727 | $admin_email = null; |
| 728 | if ( 'use-custom-email' === self::get_wp2fa_email_templates( 'email_from_setting' ) ) { |
| 729 | $admin_email = sanitize_email( self::get_wp2fa_email_templates( 'custom_from_email_address' ) ); |
| 730 | } else { |
| 731 | $admin_email = Settings_Page::get_default_email_address(); |
| 732 | } |
| 733 | |
| 734 | // These are the strings we are going to search for, as well as their respective replacements. |
| 735 | $replacements = array( |
| 736 | '{site_url}' => \esc_url( \get_bloginfo( 'url' ) ), |
| 737 | '{site_name}' => \sanitize_text_field( \get_bloginfo( 'name' ) ), |
| 738 | '{grace_period}' => \sanitize_text_field( $grace_period_string ), |
| 739 | '{user_login_name}' => \sanitize_text_field( $user->user_login ), |
| 740 | '{user_first_name}' => \sanitize_text_field( $user->user_firstname ), |
| 741 | '{user_last_name}' => \sanitize_text_field( $user->user_lastname ), |
| 742 | '{user_display_name}' => \sanitize_text_field( $user->display_name ), |
| 743 | '{login_code}' => $login_code, |
| 744 | '{2fa_settings_page_url}' => $new_page_permalink, |
| 745 | '{user_ip_address}' => esc_attr( Request_Utils::get_ip() ), |
| 746 | '{admin_email}' => $admin_email, |
| 747 | ); |
| 748 | |
| 749 | /** |
| 750 | * 3rd party plugins could change the mail strings, or provide their own. |
| 751 | * |
| 752 | * @param array $replacements - The array with all the currently supported strings. |
| 753 | */ |
| 754 | $replacements = \apply_filters( |
| 755 | WP_2FA_PREFIX . 'replacement_email_strings', |
| 756 | $replacements |
| 757 | ); |
| 758 | |
| 759 | $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input ); |
| 760 | return $final_output; |
| 761 | } |
| 762 | |
| 763 | /** |
| 764 | * Util which contextualizes the wording 'reconfigure'/'configure' as needed. |
| 765 | * |
| 766 | * @param string $input - Text we are working on. |
| 767 | * @param int|string $user_id - User id, if its needed. |
| 768 | * @param string $method_to_check - Name of the method to check for. |
| 769 | * |
| 770 | * @return string The output, with all the {strings} swapped out. |
| 771 | * |
| 772 | * @since 2.5.0 |
| 773 | */ |
| 774 | public static function contextual_reconfigure_text( $input = '', $user_id = '', $method_to_check = '' ) { |
| 775 | |
| 776 | if ( empty( trim( (string) $input ) ) ) { |
| 777 | return $input; |
| 778 | } |
| 779 | |
| 780 | $enabled_method = User_Helper::get_enabled_method_for_user( intval( $user_id ) ); |
| 781 | |
| 782 | $text = ( $enabled_method === $method_to_check ) ? \esc_html__( 'Reconfigure', 'wp-2fa' ) : \esc_html__( 'Configure', 'wp-2fa' ); |
| 783 | |
| 784 | $replacements = array( |
| 785 | '{reconfigure_or_configure_capitalized}' => $text, |
| 786 | '{reconfigure_or_configure}' => strtolower( $text ), |
| 787 | ); |
| 788 | |
| 789 | /** |
| 790 | * 3rd party plugins could change this to their own. |
| 791 | * |
| 792 | * @param array $replacements - The array with all the currently supported strings. |
| 793 | * |
| 794 | * @since 2.5.0 |
| 795 | */ |
| 796 | $replacements = \apply_filters( |
| 797 | WP_2FA_PREFIX . 'replacement_reconfigure_strings', |
| 798 | $replacements |
| 799 | ); |
| 800 | |
| 801 | return str_replace( array_keys( $replacements ), array_values( $replacements ), \wp_kses_post( $input ) ); |
| 802 | } |
| 803 | |
| 804 | /** |
| 805 | * Util replace replace a placeholder with the actual remaining grace period for a user.. |
| 806 | * |
| 807 | * @param string $input - Text we are working on. |
| 808 | * @param int $grace_expiry - Expiration time. |
| 809 | * |
| 810 | * @return string The output, with all the {strings} swapped out. |
| 811 | * |
| 812 | * @since 2.5.0 |
| 813 | */ |
| 814 | public static function replace_remaining_grace_period( $input = '', $grace_expiry = -1 ) { |
| 815 | if ( empty( trim( (string) $input ) ) || empty( trim( (string) $grace_expiry ) ) ) { |
| 816 | return sanitize_text_field( $input ); |
| 817 | } |
| 818 | |
| 819 | $replacements = array( |
| 820 | '{grace_period_remaining}' => esc_attr( Date_Time_Utils::format_grace_period_expiration_string( null, intval( $grace_expiry ) ) ), |
| 821 | ); |
| 822 | |
| 823 | return str_replace( array_keys( $replacements ), array_values( $replacements ), ( $input ) ); |
| 824 | } |
| 825 | |
| 826 | /** |
| 827 | * Util which we use to replace our {strings} with actual, useful stuff. |
| 828 | * |
| 829 | * @param string $input Text we are working on. |
| 830 | * @param WP_User $user The WP User. |
| 831 | * |
| 832 | * @return string The output, with all the {strings} swapped out. |
| 833 | * |
| 834 | * @since 2.0.0 |
| 835 | */ |
| 836 | public static function replace_wizard_strings( $input = '', $user = false ) { |
| 837 | |
| 838 | if ( ! $user ) { |
| 839 | return sanitize_text_field( $input ); |
| 840 | } |
| 841 | |
| 842 | $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) ); |
| 843 | |
| 844 | // These are the strings we are going to search for, as well as their respective replacements. |
| 845 | $replacements = array( |
| 846 | '{available_methods_count}' => intval( count( $available_methods[ User_Helper::get_user_role( $user ) ] ) ), |
| 847 | ); |
| 848 | |
| 849 | /** |
| 850 | * 3rd party plugins could change the mail strings, or provide their own. |
| 851 | * |
| 852 | * @param array $replacements - The array with all the currently supported strings. |
| 853 | */ |
| 854 | $replacements = \apply_filters( |
| 855 | WP_2FA_PREFIX . 'replacement_wizard_strings', |
| 856 | $replacements |
| 857 | ); |
| 858 | |
| 859 | $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), ( $input ) ); |
| 860 | return $final_output; |
| 861 | } |
| 862 | |
| 863 | /** |
| 864 | * If a user is trying to access anywhere other than the 2FA config area, this blocks them. |
| 865 | * |
| 866 | * @return void |
| 867 | * |
| 868 | * @since 2.0.0 |
| 869 | */ |
| 870 | public static function block_unconfigured_users_from_admin() { |
| 871 | global $pagenow; |
| 872 | |
| 873 | $user = User_Helper::get_user(); |
| 874 | if ( 0 === $user->ID ) { |
| 875 | return; |
| 876 | } |
| 877 | |
| 878 | $redirect = true; |
| 879 | |
| 880 | if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) { |
| 881 | if ( Extensions_Loader::use_proxytron() ) { |
| 882 | $redirect = User_Licensing::enable_2fa_user_setting( true ); |
| 883 | } |
| 884 | } |
| 885 | |
| 886 | |
| 887 | if ( $redirect ) { |
| 888 | $is_user_instantly_enforced = User_Helper::get_user_enforced_instantly(); |
| 889 | $grace_period_expiry_time = (int) User_Helper::get_user_expiry_date(); |
| 890 | $time_now = time(); |
| 891 | if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User_Helper::is_excluded( $user->ID ) ) { |
| 892 | |
| 893 | $has_cap = true; |
| 894 | if ( class_exists( 'WooCommerce', false ) ) { |
| 895 | |
| 896 | // Lets check if the user has the required capabilities to view the 2FA settings page (or profile page in the Admin section - dashboard). |
| 897 | $has_cap = false; |
| 898 | |
| 899 | $access_caps = array( 'edit_posts', 'manage_woocommerce', 'view_admin_dashboard' ); |
| 900 | |
| 901 | foreach ( $access_caps as $access_cap ) { |
| 902 | if ( \current_user_can( $access_cap ) ) { |
| 903 | $has_cap = true; |
| 904 | break; |
| 905 | } |
| 906 | } |
| 907 | } |
| 908 | |
| 909 | /** |
| 910 | * We should only allow: |
| 911 | * - 2FA setup wizard in the administration |
| 912 | * - custom 2FA page if enabled and created |
| 913 | * - AJAX requests originating from these 2FA setup UIs |
| 914 | */ |
| 915 | if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) { // phpcs:ignore |
| 916 | return; |
| 917 | } |
| 918 | |
| 919 | if ( \is_admin() || \is_network_admin() ) { |
| 920 | $allowed_admin_page = 'profile.php'; |
| 921 | if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) ) { // phpcs:ignore |
| 922 | return; |
| 923 | } |
| 924 | } |
| 925 | |
| 926 | if ( is_page() ) { |
| 927 | $custom_user_page_id = Settings_Utils::get_setting_role( User_Helper::get_user_role( $user ), 'custom-user-page-id' ); |
| 928 | if ( ! empty( $custom_user_page_id ) && \get_the_ID() === (int) $custom_user_page_id ) { |
| 929 | return; |
| 930 | } else { |
| 931 | $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user ); |
| 932 | if ( ! empty( $custom_user_page_id ) && \get_the_ID() === (int) $custom_user_page_id ) { |
| 933 | return; |
| 934 | } |
| 935 | } |
| 936 | } |
| 937 | |
| 938 | // force a redirect to the 2FA set-up page if it exists. |
| 939 | $custom_user_page_id = Settings_Utils::get_setting_role( User_Helper::get_user_role( $user ), 'custom-user-page-id' ); |
| 940 | if ( ! empty( $custom_user_page_id ) ) { |
| 941 | \wp_redirect( Settings::get_custom_page_link( $user ) ); |
| 942 | exit; |
| 943 | } else { |
| 944 | $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user ); |
| 945 | if ( ! empty( $custom_user_page_id ) && \get_the_ID() === (int) $custom_user_page_id ) { |
| 946 | \wp_redirect( \get_permalink( $custom_user_page_id ) ); |
| 947 | exit; |
| 948 | } |
| 949 | } |
| 950 | |
| 951 | // 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. |
| 952 | if ( ! $has_cap ) { |
| 953 | |
| 954 | // Is there WOO installed? If so, then lets try to extract the redirection rules from there. |
| 955 | if ( class_exists( 'WooCommerce', false ) ) { |
| 956 | |
| 957 | // Lets check if there is a 2FA implemented within the WOOCommerce myaccount page. |
| 958 | $items = \wc_get_account_menu_items(); |
| 959 | |
| 960 | if ( isset( $items['wp-2fa'] ) ) { |
| 961 | |
| 962 | if ( ! isset( $_GET['wp-2fa'] ) ) { |
| 963 | $url = \add_query_arg( |
| 964 | array( |
| 965 | 'wp-2fa' => '', |
| 966 | ), |
| 967 | \get_permalink( \get_option( 'woocommerce_myaccount_page_id' ) ) |
| 968 | ); |
| 969 | |
| 970 | \wp_redirect( $url ); |
| 971 | |
| 972 | exit; |
| 973 | } |
| 974 | } |
| 975 | } |
| 976 | |
| 977 | // Nothing suitable found - notify the admin and bail. |
| 978 | $transient_name = WP_2FA_PREFIX . '_notified_admin_mail_nowhere_to_redirect_' . $user->ID; |
| 979 | if ( false === \get_transient( $transient_name ) ) { |
| 980 | $subject = sprintf( |
| 981 | // translators: The username. |
| 982 | \esc_html__( |
| 983 | 'User %1$s logged in without 2FA', |
| 984 | 'wp-2fa' |
| 985 | ), |
| 986 | $user->user_login, |
| 987 | ); |
| 988 | |
| 989 | $text = sprintf( |
| 990 | // translators: The username. |
| 991 | // translators: the site name. |
| 992 | \esc_html__( |
| 993 | '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.', |
| 994 | 'wp-2fa' |
| 995 | ), |
| 996 | $user->user_login, |
| 997 | \get_bloginfo( 'name' ) |
| 998 | ); |
| 999 | $text .= '<p>' . sprintf( |
| 1000 | // translators: the settings page. |
| 1001 | // translators: the support e-mail. |
| 1002 | \esc_html__( |
| 1003 | '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.', |
| 1004 | 'wp-2fa' |
| 1005 | ), |
| 1006 | '<a href="' . \esc_url( |
| 1007 | \add_query_arg( |
| 1008 | array( |
| 1009 | 'page' => 'wp-2fa-settings', |
| 1010 | 'tab' => 'integrations', |
| 1011 | ), |
| 1012 | \network_admin_url( 'admin.php' ) |
| 1013 | ) |
| 1014 | ) . '">front-end 2FA page</a>', |
| 1015 | '<a href="mailto:support@melapress.com">support@melapress.com</a>' |
| 1016 | ) . '</p>'; |
| 1017 | |
| 1018 | Settings_Page::send_email( |
| 1019 | \get_option( 'admin_email' ), |
| 1020 | $subject, |
| 1021 | $text |
| 1022 | ); |
| 1023 | |
| 1024 | \set_transient( $transient_name, 'sent', DAY_IN_SECONDS * 2 ); |
| 1025 | } |
| 1026 | |
| 1027 | return; |
| 1028 | } |
| 1029 | |
| 1030 | // custom 2FA page is not set-up, force redirect to the wizard in administration. |
| 1031 | \wp_redirect( Settings::get_setup_page_link() ); |
| 1032 | exit; |
| 1033 | } |
| 1034 | } |
| 1035 | } |
| 1036 | |
| 1037 | /** |
| 1038 | * Returns currently stored settings |
| 1039 | * |
| 1040 | * @return array |
| 1041 | * |
| 1042 | * @since 2.0.0 |
| 1043 | */ |
| 1044 | public static function get_policy_settings() { |
| 1045 | /** |
| 1046 | * Extensions could change the stored settings value, based on custom / different / specific for role settings. |
| 1047 | * |
| 1048 | * @param array - Value of the settings. |
| 1049 | * |
| 1050 | * @since 2.0.0 |
| 1051 | */ |
| 1052 | $settings = \apply_filters( WP_2FA_PREFIX . 'policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ); |
| 1053 | |
| 1054 | return $settings; |
| 1055 | } |
| 1056 | |
| 1057 | /** |
| 1058 | * Returns currently stored White Labeling settings |
| 1059 | * |
| 1060 | * @return array |
| 1061 | * |
| 1062 | * @since 3.0.0 |
| 1063 | */ |
| 1064 | public static function get_white_labeling_settings() { |
| 1065 | /** |
| 1066 | * Extensions could change the stored settings value, based on custom / different / specific for role settings. |
| 1067 | * |
| 1068 | * @param array - Value of the settings. |
| 1069 | * |
| 1070 | * @since 3.0.0 |
| 1071 | */ |
| 1072 | $settings = \apply_filters( WP_2FA_PREFIX . 'whitelabel_settings', self::$plugin_settings[ \WP_2FA_WHITE_LABEL_SETTINGS_NAME ] ); |
| 1073 | |
| 1074 | return $settings; |
| 1075 | } |
| 1076 | |
| 1077 | /** |
| 1078 | * Checks the action parameter against given list of actions |
| 1079 | * |
| 1080 | * @return bool |
| 1081 | * |
| 1082 | * @since 2.0.0 |
| 1083 | */ |
| 1084 | private static function action_check() { |
| 1085 | if ( ! isset( $_REQUEST['action'] ) ) { |
| 1086 | return false; |
| 1087 | } |
| 1088 | $actions_array = array( |
| 1089 | 'send_authentication_setup_email', |
| 1090 | 'validate_authcode_via_ajax', |
| 1091 | 'heartbeat', |
| 1092 | 'regenerate_authentication_key', |
| 1093 | 'send_backup_codes_email', |
| 1094 | 'register_user_twilio', |
| 1095 | 'register_user_clickatell', |
| 1096 | ); |
| 1097 | |
| 1098 | /** |
| 1099 | * Allows 3rd party providers to add their own actions to the check. |
| 1100 | * |
| 1101 | * @param array $actions_array - Array with the default actions. |
| 1102 | * |
| 1103 | * @since 2.0.0 |
| 1104 | */ |
| 1105 | $actions_array = \apply_filters( WP_2FA_PREFIX . 'actions_check', $actions_array ); |
| 1106 | |
| 1107 | $action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ); |
| 1108 | |
| 1109 | return in_array( $action, $actions_array, true ); |
| 1110 | } |
| 1111 | |
| 1112 | /** |
| 1113 | * Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings. |
| 1114 | * |
| 1115 | * @param array $settings - The settings values. |
| 1116 | * @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. |
| 1117 | * @param string $settings_name - The name of the settings to extract. |
| 1118 | * |
| 1119 | * @since 2.0.0 |
| 1120 | */ |
| 1121 | public static function update_plugin_settings( $settings, $skip_option_save = false, $settings_name = WP_2FA_POLICY_SETTINGS_NAME ) { |
| 1122 | // update local copy of settings. |
| 1123 | self::$plugin_settings[ $settings_name ] = $settings; |
| 1124 | |
| 1125 | if ( ! $skip_option_save ) { |
| 1126 | // update the database option itself. |
| 1127 | Settings_Utils::update_option( $settings_name, $settings ); |
| 1128 | } |
| 1129 | |
| 1130 | if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) { |
| 1131 | // Create a hash for comparison when we interact with a user. |
| 1132 | $settings_hash = Settings_Utils::create_settings_hash( self::get_policy_settings() ); |
| 1133 | Settings_Utils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash ); |
| 1134 | } |
| 1135 | } |
| 1136 | |
| 1137 | /** |
| 1138 | * Getter for the secret key of the plugin for the current instance |
| 1139 | * |
| 1140 | * Note: that is legacy code and will be removed. |
| 1141 | * |
| 1142 | * @return string |
| 1143 | * |
| 1144 | * @since 2.0.0 |
| 1145 | */ |
| 1146 | public static function get_secret_key() { |
| 1147 | if ( null === self::$secret_key ) { |
| 1148 | if ( ! defined( File_Writer::SECRET_NAME ) ) { |
| 1149 | self::check_for_key(); |
| 1150 | } else { |
| 1151 | self::$secret_key = constant( File_Writer::SECRET_NAME ); |
| 1152 | } |
| 1153 | } |
| 1154 | |
| 1155 | return self::$secret_key; |
| 1156 | } |
| 1157 | |
| 1158 | /** |
| 1159 | * Checks if the wp-config.php file is writable, show notice to the admin if it is not |
| 1160 | * |
| 1161 | * @return void |
| 1162 | * |
| 1163 | * @since 2.4.0 |
| 1164 | */ |
| 1165 | public static function wp_not_writable() { |
| 1166 | |
| 1167 | if ( ! \defined( 'WP2FA_SECRET_IS_IN_DB' ) || true !== WP2FA_SECRET_IS_IN_DB ) { |
| 1168 | return; |
| 1169 | } |
| 1170 | |
| 1171 | $dismissed = (bool) Settings_Utils::get_option( 'remove_store_salt_in_wp_config_message', false ); |
| 1172 | |
| 1173 | if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) && ! $dismissed ) { |
| 1174 | $whitelist_admin_pages = array( |
| 1175 | 'wp-2fa_page_wp-2fa-settings', |
| 1176 | 'wp-2fa_page_wp-2fa-settings-network', |
| 1177 | 'toplevel_page_wp-2fa-policies', |
| 1178 | 'toplevel_page_wp-2fa-policies-network', |
| 1179 | 'wp-2fa_page_wp-2fa-help-contact-us', |
| 1180 | 'wp-2fa_page_wp-2fa-help-contact-us-network', |
| 1181 | 'wp-2fa_page_wp-2fa-policies-account', |
| 1182 | 'wp-2fa_page_wp-2fa-policies-account-network', |
| 1183 | 'wp-2fa_page_wp-2fa-reports', |
| 1184 | 'wp-2fa_page_wp-2fa-reports-network', |
| 1185 | ); |
| 1186 | $admin_page = \get_current_screen(); |
| 1187 | if ( in_array( $admin_page->base, $whitelist_admin_pages, true ) ) { |
| 1188 | ?> |
| 1189 | <div class="notice notice-warning" id="config-update-notice"> |
| 1190 | <?php |
| 1191 | $message = sprintf( |
| 1192 | '<p>%1$s <a href="https://melapress.com/support/kb/wp-2fa-add-2fa-plugin-encryption-key-wp-config/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=encryption_key_in_wp_config" noopener target="_blank">%2$s</a><br>%3$s</p>', |
| 1193 | \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>' . |
| 1194 | \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>' . |
| 1195 | \esc_html__( 'Option B) Add the encryption key to the wp-config.php file yourself by ', 'wp-2fa' ), |
| 1196 | \esc_html__( 'following these instructions.', 'wp-2fa' ) . '<br>', |
| 1197 | \esc_html__( 'Once you complete any of the above, please click the button below.', 'wp-2fa' ) |
| 1198 | ); |
| 1199 | echo $message; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 1200 | ?> |
| 1201 | <p><button id="salt-update" type="button"> |
| 1202 | <span><?php \esc_html_e( 'Write key to file now / Check for the key in file', 'wp-2fa' ); ?></span> |
| 1203 | </button><button style="margin-left: 15px;" id="dismiss-salt-update" type="button"> |
| 1204 | <span><?php \esc_html_e( 'I am aware of the risks. Please do not alert me again about this.', 'wp-2fa' ); ?></span> |
| 1205 | </button></p> |
| 1206 | </div> |
| 1207 | <script> |
| 1208 | jQuery(document).ready(function($) { |
| 1209 | jQuery(document).on('click', '#salt-update', function(event) { |
| 1210 | event.preventDefault(); |
| 1211 | const ajaxURL = (typeof wp2faWizardData !== "undefined") ? wp2faWizardData.ajaxURL : ajaxurl; |
| 1212 | const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-set-salt-nonce' ) ); ?>'; |
| 1213 | jQuery.ajax({ |
| 1214 | url: ajaxURL, |
| 1215 | method: 'POST', |
| 1216 | data: { |
| 1217 | action: 'set_salt_key', |
| 1218 | _wpnonce: nonceValue |
| 1219 | }, |
| 1220 | success: function(data) { |
| 1221 | if (data.success) { |
| 1222 | jQuery('#config-update-notice').remove(); |
| 1223 | } else { |
| 1224 | alert(data.data); |
| 1225 | } |
| 1226 | }, |
| 1227 | error: function(data) { |
| 1228 | alert(data.responseJSON.data[0].message); |
| 1229 | } |
| 1230 | }); |
| 1231 | }); |
| 1232 | |
| 1233 | jQuery(document).on('click', '#dismiss-salt-update', function(event) { |
| 1234 | event.preventDefault(); |
| 1235 | const ajaxURL = (typeof wp2faWizardData !== "undefined") ? wp2faWizardData.ajaxURL : ajaxurl; |
| 1236 | const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-unset-salt-nonce' ) ); ?>'; |
| 1237 | jQuery.ajax({ |
| 1238 | url: ajaxURL, |
| 1239 | method: 'POST', |
| 1240 | data: { |
| 1241 | action: 'unset_salt_key', |
| 1242 | _wpnonce: nonceValue |
| 1243 | }, |
| 1244 | success: function(data) { |
| 1245 | if (data.success) { |
| 1246 | jQuery('#config-update-notice').remove(); |
| 1247 | } else { |
| 1248 | alert(data.data); |
| 1249 | } |
| 1250 | }, |
| 1251 | error: function(data) { |
| 1252 | alert(data.responseJSON.data[0].message); |
| 1253 | } |
| 1254 | }); |
| 1255 | }); |
| 1256 | }); |
| 1257 | </script> |
| 1258 | <?php |
| 1259 | } |
| 1260 | } |
| 1261 | } |
| 1262 | |
| 1263 | /** |
| 1264 | * Remove the user meta related with the code has been sent to the user. |
| 1265 | * That is so we can lower the security by giving the option not to resend codes, so eventual brute force could succeed. |
| 1266 | * The setting name - brute_force_disable |
| 1267 | * |
| 1268 | * @return void |
| 1269 | * |
| 1270 | * @since 2.5.0 |
| 1271 | */ |
| 1272 | public static function clear_user_after_login() { |
| 1273 | User_Helper::remove_meta( WP_2FA_PREFIX . 'code_sent' ); |
| 1274 | } |
| 1275 | |
| 1276 | /** |
| 1277 | * Determine the plugin type. |
| 1278 | * |
| 1279 | * @return string |
| 1280 | * |
| 1281 | * @since 3.0.0 |
| 1282 | */ |
| 1283 | public static function determine_plugin_type(): string { |
| 1284 | |
| 1285 | if ( null === self::$plugin_type ) { |
| 1286 | |
| 1287 | self::$plugin_type = 'free'; |
| 1288 | // Check if the plugin is a premium version. |
| 1289 | if ( class_exists( '\WP2FA\Extensions_Loader', false ) ) { |
| 1290 | self::$plugin_type = 'premium'; |
| 1291 | } |
| 1292 | } |
| 1293 | |
| 1294 | return self::$plugin_type; |
| 1295 | } |
| 1296 | |
| 1297 | |
| 1298 | /** |
| 1299 | * Checks and sets the global wp2fa salt |
| 1300 | * |
| 1301 | * @return void |
| 1302 | * |
| 1303 | * @since 2.4.0 |
| 1304 | */ |
| 1305 | private static function check_for_key() { |
| 1306 | self::$secret_key = Settings_Utils::get_option( 'secret_key' ); |
| 1307 | if ( empty( self::$secret_key ) ) { |
| 1308 | self::$secret_key = base64_encode( Open_SSL::secure_random() ); // phpcs:ignore |
| 1309 | if ( ! File_Writer::save_secret_key( self::$secret_key ) ) { |
| 1310 | Settings_Utils::update_option( 'secret_key', self::$secret_key ); |
| 1311 | } |
| 1312 | } |
| 1313 | } |
| 1314 | } |
| 1315 | } |
| 1316 |