| 1 |
<?php |
| 2 |
|
| 3 |
namespace WP_Defender\Controller; |
| 4 |
|
| 5 |
use Calotes\Component\Request; |
| 6 |
use Calotes\Helper\Array_Cache; |
| 7 |
use Calotes\Helper\HTTP; |
| 8 |
use Calotes\Helper\Route; |
| 9 |
use WP_Defender\Behavior\WPMUDEV; |
| 10 |
use WP_Defender\Component\Config\Config_Hub_Helper; |
| 11 |
use WP_Defender\Component\Two_Factor\Providers\Webauthn; |
| 12 |
use WP_Defender\Component\Two_Factor\Providers\Totp; |
| 13 |
use WP_Defender\Controller; |
| 14 |
use WP_Defender\Model\Setting\Two_Fa; |
| 15 |
use Calotes\Component\Response; |
| 16 |
use WP_Defender\Component\Two_Fa as Two_Fa_Component; |
| 17 |
use WP_Defender\Component\Two_Factor\Providers\Backup_Codes; |
| 18 |
use WP_Defender\Component\Two_Factor\Providers\Fallback_Email; |
| 19 |
use WP_Defender\Traits\Webauthn as Webauthn_Trait; |
| 20 |
|
| 21 |
class Two_Factor extends Controller { |
| 22 |
use Webauthn_Trait; |
| 23 |
|
| 24 |
public $slug = 'wdf-2fa'; |
| 25 |
|
| 26 |
/** |
| 27 |
* @var Two_Fa |
| 28 |
*/ |
| 29 |
protected $model; |
| 30 |
|
| 31 |
/** |
| 32 |
* @var mixed|Two_Fa_Component |
| 33 |
*/ |
| 34 |
protected $service; |
| 35 |
|
| 36 |
/** |
| 37 |
* @var array |
| 38 |
*/ |
| 39 |
protected $compatibility_notices = array(); |
| 40 |
|
| 41 |
/** |
| 42 |
* @var \WP_Defender\Component\Password_Protection |
| 43 |
*/ |
| 44 |
protected $password_protection_service; |
| 45 |
|
| 46 |
public function __construct() { |
| 47 |
$this->register_page( |
| 48 |
esc_html__( '2FA', 'wpdef' ) . ' ' . $this->menu_suffix(), |
| 49 |
$this->slug, |
| 50 |
array( |
| 51 |
&$this, |
| 52 |
'main_view', |
| 53 |
), |
| 54 |
$this->parent_slug |
| 55 |
); |
| 56 |
add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ) ); |
| 57 |
$this->register_routes(); |
| 58 |
$this->service = wd_di()->get( Two_Fa_Component::class ); |
| 59 |
$this->model = wd_di()->get( Two_Fa::class ); |
| 60 |
|
| 61 |
$this->password_protection_service = wd_di()->get( \WP_Defender\Component\Password_Protection::class ); |
| 62 |
|
| 63 |
add_action( 'update_option_jetpack_active_modules', array( &$this, 'listen_for_jetpack_option' ), 10, 3 ); |
| 64 |
|
| 65 |
if ( $this->model->enabled ) { |
| 66 |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 67 |
$is_jetpack_sso = $this->service->is_jetpack_sso(); |
| 68 |
$is_tml = $this->service->is_tml(); |
| 69 |
add_action( 'admin_init', array( $this->service, 'get_providers' ) ); |
| 70 |
add_action( 'pre_get_users', array( &$this, 'filter_users_by_2fa' ) ); |
| 71 |
add_action( 'show_user_profile', array( &$this, 'show_user_profile' ) ); |
| 72 |
add_action( 'profile_update', array( &$this, 'profile_update' ) ); |
| 73 |
|
| 74 |
if ( ! defined( 'DOING_AJAX' ) && ! $is_jetpack_sso && ! $is_tml ) { |
| 75 |
add_filter( 'wp_authenticate_user', array( &$this, 'maybe_show_otp_form' ), 9, 2 ); |
| 76 |
add_action( 'set_logged_in_cookie', array( &$this, 'store_session_key' ) ); |
| 77 |
add_action( 'login_form_defender-verify-otp', array( &$this, 'verify_otp_login_time' ) ); |
| 78 |
} else { |
| 79 |
if ( $is_jetpack_sso ) { |
| 80 |
$this->compatibility_notices[] = __( "We've detected a conflict with Jetpack's Wordpress.com Log In feature. Please disable it and return to this page to continue setup.", 'wpdef' ); |
| 81 |
} |
| 82 |
if ( $is_tml ) { |
| 83 |
$this->compatibility_notices[] = __( "We've detected a conflict with Theme my login. Please disable it and return to this page to continue setup.", 'wpdef' ); |
| 84 |
} |
| 85 |
} |
| 86 |
// Force auth redirect. |
| 87 |
add_action( 'current_screen', array( &$this, 'maybe_redirect_to_show_2fa_enabler' ), 1 ); |
| 88 |
if ( is_multisite() ) { |
| 89 |
add_filter( 'wpmu_users_columns', array( &$this, 'alter_users_table' ) ); |
| 90 |
} else { |
| 91 |
add_filter( 'manage_users_columns', array( &$this, 'alter_users_table' ) ); |
| 92 |
} |
| 93 |
add_filter( 'manage_users_custom_column', array( &$this, 'alter_user_table_row' ), 10, 3 ); |
| 94 |
add_filter( 'ms_shortcode_ajax_login', array( &$this, 'm2_no_ajax' ) ); |
| 95 |
// Todo: add the verify for filter 'login_redirect'. |
| 96 |
|
| 97 |
$this->woocommerce_hooks(); |
| 98 |
} |
| 99 |
} |
| 100 |
|
| 101 |
/** |
| 102 |
* Show information about new feature. |
| 103 |
* |
| 104 |
* @since 3.0.0 |
| 105 |
* @retun string |
| 106 |
*/ |
| 107 |
public function menu_suffix() { |
| 108 |
return '<span style="padding: 2px 6px;border-radius: 9px;background-color: #17A8E3;color: #FFF;font-size: 8px;letter-spacing: -0.25px;text-transform: uppercase;vertical-align: middle;">' . __( 'NEW', 'wpdef' ) . '</span>'; |
| 109 |
} |
| 110 |
|
| 111 |
/** |
| 112 |
* We have some feature conflict with jetpack, so listen to know when Defender can on. |
| 113 |
* |
| 114 |
* @param $old_value |
| 115 |
* @param $value |
| 116 |
* @param $option |
| 117 |
*/ |
| 118 |
public function listen_for_jetpack_option( $old_value, $value, $option ) { |
| 119 |
if ( false !== array_search( 'sso', $value ) ) { |
| 120 |
$this->model->mark_as_conflict( 'jetpack/jetpack.php' ); |
| 121 |
} else { |
| 122 |
$this->model->mark_as_un_conflict( 'jetpack/jetpack.php' ); |
| 123 |
} |
| 124 |
} |
| 125 |
|
| 126 |
/** |
| 127 |
* If force redirect enabled, then we should check and redirect to profile page until the 2FA enabled. |
| 128 |
*/ |
| 129 |
public function maybe_redirect_to_show_2fa_enabler() { |
| 130 |
$user = wp_get_current_user(); |
| 131 |
if ( ! is_object( $user ) ) { |
| 132 |
return; |
| 133 |
} |
| 134 |
// Is User role from common list checked? |
| 135 |
if ( false === $this->is_auth_enabled_for_user_role( $user ) ) { |
| 136 |
return; |
| 137 |
} |
| 138 |
// Is 'Force Authentication' checked? |
| 139 |
if ( false === $this->model->force_auth ) { |
| 140 |
return; |
| 141 |
} |
| 142 |
// Is User role from forced list checked? |
| 143 |
if ( ! $this->service->is_force_auth_enable_for( $user->ID, $this->model->force_auth_roles ) ) { |
| 144 |
return; |
| 145 |
} |
| 146 |
// Is TOTP saved with a passcode? |
| 147 |
if ( ! empty( $this->service->get_available_providers_for_user( $user ) ) ) { |
| 148 |
return; |
| 149 |
} |
| 150 |
$screen = get_current_screen(); |
| 151 |
if ( 'profile' !== $screen->id ) { |
| 152 |
wp_safe_redirect( admin_url( 'profile.php' ) . '#defender-security' ); |
| 153 |
exit; |
| 154 |
} |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* Add a column in the users table, column will be last. |
| 159 |
* |
| 160 |
* @param array $columns |
| 161 |
* |
| 162 |
* @return array |
| 163 |
*/ |
| 164 |
public function alter_users_table( $columns ) { |
| 165 |
$columns = array_slice( $columns, 0, count( $columns ) - 1 ) + array( |
| 166 |
'defender-two-fa' => __( 'Two Factor', 'wpdef' ), |
| 167 |
) + array_slice( $columns, count( $columns ) - 1 ); |
| 168 |
|
| 169 |
return $columns; |
| 170 |
} |
| 171 |
|
| 172 |
/** |
| 173 |
* @param $val |
| 174 |
* @param $column_name |
| 175 |
* @param $user_id |
| 176 |
* |
| 177 |
* @return string |
| 178 |
* @since 2.8.1 Update return value. |
| 179 |
*/ |
| 180 |
public function alter_user_table_row( $val, $column_name, $user_id ) { |
| 181 |
if ( 'defender-two-fa' !== $column_name ) { |
| 182 |
return $val; |
| 183 |
} |
| 184 |
$provider_slug = get_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, true ); |
| 185 |
$provider = $this->service->get_provider_by_slug( $provider_slug ); |
| 186 |
if ( is_wp_error( $provider ) ) { |
| 187 |
return ''; |
| 188 |
} |
| 189 |
|
| 190 |
return $provider->get_user_label(); |
| 191 |
} |
| 192 |
|
| 193 |
/** |
| 194 |
* Retrieve the backup code if lost phone. |
| 195 |
* |
| 196 |
* @param Request $request |
| 197 |
* |
| 198 |
* @return Response |
| 199 |
* @defender_route |
| 200 |
* @is_public |
| 201 |
*/ |
| 202 |
public function send_backup_code( Request $request ) { |
| 203 |
$data = $request->get_data(); |
| 204 |
$token = $data['token']; |
| 205 |
$ret = $this->service->send_otp_to_email( $token ); |
| 206 |
if ( false === $ret ) { |
| 207 |
return new Response( |
| 208 |
false, |
| 209 |
array( |
| 210 |
'message' => __( 'Please try again.', 'wpdef' ), |
| 211 |
) |
| 212 |
); |
| 213 |
} |
| 214 |
|
| 215 |
if ( is_wp_error( $ret ) ) { |
| 216 |
return new Response( |
| 217 |
false, |
| 218 |
array( |
| 219 |
'message' => $ret->get_error_message(), |
| 220 |
) |
| 221 |
); |
| 222 |
} |
| 223 |
|
| 224 |
return new Response( |
| 225 |
true, |
| 226 |
array( |
| 227 |
'message' => __( 'Your code has been sent to your email.', 'wpdef' ), |
| 228 |
) |
| 229 |
); |
| 230 |
} |
| 231 |
|
| 232 |
/** |
| 233 |
* Verify the OTP after user login successful. |
| 234 |
* |
| 235 |
* @return void |
| 236 |
*/ |
| 237 |
public function verify_otp_login_time() { |
| 238 |
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] ) { |
| 239 |
return; |
| 240 |
} |
| 241 |
if ( empty( $_POST['_wpnonce'] ) ) { |
| 242 |
return; |
| 243 |
} |
| 244 |
if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'verify_otp' ) ) { |
| 245 |
return; |
| 246 |
} |
| 247 |
// Base params. |
| 248 |
$params = array( |
| 249 |
'password' => $this->password_protection_service->get_submitted_password(), |
| 250 |
'user_id' => null, |
| 251 |
); |
| 252 |
// Find the user first. |
| 253 |
$token = HTTP::post( 'login_token' ); |
| 254 |
$query = new \WP_User_Query( |
| 255 |
array( |
| 256 |
'blog_id' => 0, |
| 257 |
'meta_key' => 'defender_two_fa_token', |
| 258 |
'meta_value' => $token, |
| 259 |
) |
| 260 |
); |
| 261 |
|
| 262 |
if ( 0 === $query->get_total() ) { |
| 263 |
$params['error'] = new \WP_Error( 'opt_fail', __( 'Invalid request', 'wpdef' ) ); |
| 264 |
$this->render_otp_screen( $params ); |
| 265 |
} |
| 266 |
$user = $query->get_results()[0]; |
| 267 |
$params['user_id'] = $user->ID; |
| 268 |
$redirect = HTTP::post( 'redirect_to', admin_url() ); |
| 269 |
$params['token'] = uniqid( 'two_fa' ); |
| 270 |
update_user_meta( $user->ID, 'defender_two_fa_token', $params['token'] ); |
| 271 |
// Get Auth method. |
| 272 |
$auth_method = HTTP::post( 'auth_method' ); |
| 273 |
if ( empty( $auth_method ) ) { |
| 274 |
$auth_method = $this->service->get_default_provider_slug_for_user( $user->ID ); |
| 275 |
} |
| 276 |
$params['default_slug'] = $auth_method; |
| 277 |
// Get provider object. |
| 278 |
$provider = $this->service->get_provider_by_slug( $auth_method ); |
| 279 |
if ( is_wp_error( $provider ) ) { |
| 280 |
$params['error'] = $provider; |
| 281 |
$this->render_otp_screen( $params ); |
| 282 |
} |
| 283 |
$result = $provider->validate_authentication( $user ); |
| 284 |
if ( is_wp_error( $result ) ) { |
| 285 |
$params['error'] = $result; |
| 286 |
$this->render_otp_screen( $params ); |
| 287 |
} |
| 288 |
if ( $result ) { |
| 289 |
// Clean token. |
| 290 |
delete_user_meta( $user->ID, 'defender_two_fa_token' ); |
| 291 |
|
| 292 |
$password = HTTP::post( 'password' ); |
| 293 |
|
| 294 |
$is_weak_password = $this->password_protection_service->is_weak_password( $user, $password ); |
| 295 |
if ( true === $is_weak_password ) { |
| 296 |
$this->password_protection_service->do_weak_reset( $user, $password ); |
| 297 |
} elseif ( $this->password_protection_service->is_force_reset( $user ) ) { |
| 298 |
$this->password_protection_service->do_force_reset( $user, $password ); |
| 299 |
} else { |
| 300 |
$user_id = $user->ID; |
| 301 |
// Set active user. |
| 302 |
wp_set_current_user( $user_id, $user->user_login ); |
| 303 |
wp_set_auth_cookie( $user_id, true ); |
| 304 |
|
| 305 |
/** |
| 306 |
* Fires after successful login via 2fa. |
| 307 |
* |
| 308 |
* @since 2.6.1 |
| 309 |
* |
| 310 |
* @param int $user_id |
| 311 |
*/ |
| 312 |
do_action( 'wpmu_2fa_login', $user_id ); |
| 313 |
|
| 314 |
if ( isset( $_REQUEST['interim-login'] ) ) { |
| 315 |
$params['interim_login'] = 'success'; |
| 316 |
$params['message'] = '<p class="message">' . __( 'You have logged in successfully.', 'wpdef' ) . '</p>'; |
| 317 |
$this->render_otp_screen( $params ); |
| 318 |
exit; |
| 319 |
} else { |
| 320 |
$redirect = apply_filters( |
| 321 |
'login_redirect', |
| 322 |
$redirect, |
| 323 |
$this->redirect_url(), |
| 324 |
$user |
| 325 |
); |
| 326 |
wp_safe_redirect( $redirect ); |
| 327 |
exit; |
| 328 |
} |
| 329 |
} |
| 330 |
} |
| 331 |
|
| 332 |
$params['error'] = new \WP_Error( |
| 333 |
'opt_fail', |
| 334 |
__( 'Whoops, the passcode you entered was incorrect or expired.', 'wpdef' ) |
| 335 |
); |
| 336 |
$this->render_otp_screen( $params ); |
| 337 |
} |
| 338 |
|
| 339 |
/** |
| 340 |
* Render otp form. Required conditions for the current user: |
| 341 |
* user data is not empty, |
| 342 |
* password matches the user, |
| 343 |
* user role is checked on 2FA settings, |
| 344 |
* user has at least one 2FA auth method available. |
| 345 |
* |
| 346 |
* @param \WP_User $user Object of the logged-in user. |
| 347 |
* @param string $password Plain password string. |
| 348 |
*/ |
| 349 |
public function maybe_show_otp_form( $user, $password ) { |
| 350 |
$params = []; |
| 351 |
if ( |
| 352 |
! empty( $user ) && ! empty( $password ) && $user instanceof \WP_User |
| 353 |
&& wp_check_password( $password, $user->data->user_pass, $user->ID ) |
| 354 |
&& $this->is_auth_enabled_for_user_role( $user ) |
| 355 |
&& ! empty( $this->service->get_available_providers_for_user( $user ) ) |
| 356 |
) { |
| 357 |
$cookie = Array_Cache::get( 'auth_cookie', '2fa' ); |
| 358 |
if ( null !== $cookie ) { |
| 359 |
// Clear all session data if any. |
| 360 |
$session = \WP_Session_Tokens::get_instance( $user->ID ); |
| 361 |
$session->destroy( $cookie['token'] ); |
| 362 |
} |
| 363 |
// Prevent user to login, and show otp screen. |
| 364 |
wp_clear_auth_cookie(); |
| 365 |
// All goods, we'll need to create a unique token to mark this user. |
| 366 |
$params['token'] = uniqid( 'two_fa' ); |
| 367 |
update_user_meta( $user->ID, 'defender_two_fa_token', $params['token'] ); |
| 368 |
$params['password'] = $password; |
| 369 |
$params['user_id'] = $user->ID; |
| 370 |
// Get default provider. |
| 371 |
$params['default_slug'] = $this->service->get_default_provider_slug_for_user( $user->ID ); |
| 372 |
if ( Fallback_Email::$slug === $params['default_slug'] ) { |
| 373 |
$result = $this->service->send_otp_to_email( $params['token'] ); |
| 374 |
if ( is_wp_error( $result ) ) { |
| 375 |
$params['error'] = $result; |
| 376 |
$this->render_otp_screen( $params ); |
| 377 |
} |
| 378 |
} |
| 379 |
$this->render_otp_screen( $params ); |
| 380 |
} |
| 381 |
|
| 382 |
return $user; |
| 383 |
} |
| 384 |
|
| 385 |
/** |
| 386 |
* Render the OTP screen after login successful. |
| 387 |
* |
| 388 |
* @param array $params |
| 389 |
*/ |
| 390 |
private function render_otp_screen( $params = array() ) { |
| 391 |
wp_enqueue_script( 'jquery' ); |
| 392 |
wp_enqueue_style( 'defender-otp-screen', defender_asset_url( '/assets/css/otp.css' ) ); |
| 393 |
$params['redirect_to'] = $this->redirect_url(); |
| 394 |
if ( ! isset( $params['error'] ) ) { |
| 395 |
$params['error'] = null; |
| 396 |
} |
| 397 |
// If this goes here then the current user is ok, need to show the 2 auth. |
| 398 |
$this->attach_behavior( 'wpmudev', WPMUDEV::class ); |
| 399 |
$custom_graphic = ''; |
| 400 |
$settings = new Two_Fa(); |
| 401 |
if ( $this->is_pro() && $settings->custom_graphic && '' !== $settings->custom_graphic_url ) { |
| 402 |
$custom_graphic = $settings->custom_graphic_url; |
| 403 |
} |
| 404 |
$this->detach_behavior( 'wpmudev' ); |
| 405 |
$params['custom_graphic'] = $custom_graphic; |
| 406 |
|
| 407 |
$routes = $this->dump_routes_and_nonces(); |
| 408 |
$params['providers'] = array(); |
| 409 |
if ( isset( $params['user_id'] ) ) { |
| 410 |
$user = get_user_by( 'id', $params['user_id'] ); |
| 411 |
if ( is_object( $user ) ) { |
| 412 |
$params['providers'] = $this->service->get_available_providers_for_user( $user ); |
| 413 |
// Get default provider. |
| 414 |
if ( empty( $params['default_slug'] ) ) { |
| 415 |
$params['default_slug'] = $this->service->get_default_provider_slug_for_user( $user->ID ); |
| 416 |
} |
| 417 |
} |
| 418 |
} |
| 419 |
if ( true === array_key_exists( Webauthn::$slug, $params['providers'] ) ) { |
| 420 |
wp_enqueue_style( 'defender-biometric-login-screen', defender_asset_url( '/assets/css/biometric.css' ), array(), DEFENDER_VERSION ); |
| 421 |
wp_enqueue_script( |
| 422 |
'wpdef_webauthn_common_script', |
| 423 |
plugins_url( 'assets/js/webauthn-common.js', WP_DEFENDER_FILE ), |
| 424 |
array(), |
| 425 |
DEFENDER_VERSION, |
| 426 |
true |
| 427 |
); |
| 428 |
wp_enqueue_script( |
| 429 |
'defender-biometric-login-script', |
| 430 |
plugins_url( 'assets/js/biometric-login.js', WP_DEFENDER_FILE ), |
| 431 |
array( |
| 432 |
'jquery', |
| 433 |
'wpdef_webauthn_common_script', |
| 434 |
), |
| 435 |
DEFENDER_VERSION, |
| 436 |
true |
| 437 |
); |
| 438 |
$webauthn_controller = wd_di()->get( \WP_Defender\Controller\Webauthn::class ); |
| 439 |
wp_localize_script( |
| 440 |
'defender-biometric-login-script', |
| 441 |
'webauthn', |
| 442 |
array( |
| 443 |
'admin_url' => admin_url( 'admin-ajax.php' ), |
| 444 |
'nonce' => wp_create_nonce( 'wpdef_webauthn' ), |
| 445 |
'i18n' => $webauthn_controller->get_translations(), |
| 446 |
'username' => ! empty( $user->user_login ) ? $user->user_login : '', |
| 447 |
'two_fa_default_provider' => isset( $params['default_slug'] ) ? $params['default_slug'] : '', |
| 448 |
'provider_slug' => Webauthn::$slug, |
| 449 |
'error' => $params['error'], |
| 450 |
) |
| 451 |
); |
| 452 |
} |
| 453 |
$params['action_fallback_email'] = admin_url( 'admin-ajax.php' ) . sprintf( |
| 454 |
'?action=%s&route=%s&_def_nonce=%s', |
| 455 |
defender_base_action(), |
| 456 |
$this->check_route( $routes['routes']['send_backup_code'] ), |
| 457 |
$routes['nonces']['send_backup_code'] |
| 458 |
); |
| 459 |
$this->render_partial( 'two-fa/otp', $params ); |
| 460 |
exit; |
| 461 |
} |
| 462 |
|
| 463 |
/** |
| 464 |
* Cache the auth cookie. |
| 465 |
* |
| 466 |
* @param $cookie |
| 467 |
*/ |
| 468 |
public function store_session_key( $cookie ) { |
| 469 |
// Clear login cookie to ensure nonce consistency. |
| 470 |
if ( ! is_user_logged_in() && isset( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) { |
| 471 |
unset( $_COOKIE[ LOGGED_IN_COOKIE ] ); |
| 472 |
} |
| 473 |
|
| 474 |
$cookie = wp_parse_auth_cookie( $cookie, 'logged_in' ); |
| 475 |
Array_Cache::set( 'auth_cookie', $cookie, '2fa' ); |
| 476 |
} |
| 477 |
|
| 478 |
|
| 479 |
/** |
| 480 |
* Disable 2FA TOTP method for the current user. It's not from the list of routes. |
| 481 |
* |
| 482 |
* @return Response |
| 483 |
* @defender_route |
| 484 |
* @is_public |
| 485 |
*/ |
| 486 |
public function disable_totp() { |
| 487 |
$user_id = get_current_user_id(); |
| 488 |
update_user_meta( $user_id, 'defenderAuthOn', 0 ); |
| 489 |
delete_user_meta( $user_id, 'defenderAuthSecret' ); |
| 490 |
// Remove TOTP from enabled providers. |
| 491 |
$enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true ); |
| 492 |
if ( isset( $enabled_providers ) && ! empty( $enabled_providers ) ) { |
| 493 |
foreach ( $enabled_providers as $key => $slug ) { |
| 494 |
if ( Totp::$slug === $slug ) { |
| 495 |
unset( $enabled_providers[ $key ] ); |
| 496 |
break; |
| 497 |
} |
| 498 |
} |
| 499 |
} else { |
| 500 |
$enabled_providers = ''; |
| 501 |
} |
| 502 |
update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers ); |
| 503 |
|
| 504 |
return new Response( true, array() ); |
| 505 |
} |
| 506 |
|
| 507 |
/** |
| 508 |
* Verify the OTP and enable 2fa, use in profile.php. It's not from the list of routes. |
| 509 |
* |
| 510 |
* @param Request $request |
| 511 |
* |
| 512 |
* @return void|Response |
| 513 |
* @defender_route |
| 514 |
* @is_public |
| 515 |
*/ |
| 516 |
public function verify_otp_for_enabling( Request $request ) { |
| 517 |
if ( ! is_user_logged_in() ) { |
| 518 |
return; |
| 519 |
} |
| 520 |
$data = $request->get_data(); |
| 521 |
$otp = $data['otp'] ?? false; |
| 522 |
if ( false === $otp || strlen( $otp ) < 6 ) { |
| 523 |
return new Response( |
| 524 |
false, |
| 525 |
array( |
| 526 |
'message' => __( 'Please input a valid OTP code.', 'wpdef' ), |
| 527 |
) |
| 528 |
); |
| 529 |
} |
| 530 |
if ( $this->service->verify_otp( $otp ) ) { |
| 531 |
$user_id = get_current_user_id(); |
| 532 |
$this->service->enable_otp( $user_id ); |
| 533 |
$totp_slug = Totp::$slug; |
| 534 |
// Add TOTP to enabled providers. |
| 535 |
$enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true ); |
| 536 |
if ( isset( $enabled_providers ) && ! empty( $enabled_providers ) ) { |
| 537 |
// Array of enabled providers is not empty now. |
| 538 |
if ( ! in_array( Totp::$slug, $enabled_providers, true ) ) { |
| 539 |
$enabled_providers[] = $totp_slug; |
| 540 |
update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers ); |
| 541 |
} |
| 542 |
} else { |
| 543 |
// Array of enabled providers is empty now. |
| 544 |
update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, array( $totp_slug ) ); |
| 545 |
} |
| 546 |
// If no default provider then add TOTP as it. |
| 547 |
$default_provider = get_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, true ); |
| 548 |
if ( empty( $default_provider ) ) { |
| 549 |
update_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, $totp_slug ); |
| 550 |
} |
| 551 |
|
| 552 |
return new Response( true, array() ); |
| 553 |
} else { |
| 554 |
return new Response( |
| 555 |
false, |
| 556 |
array( |
| 557 |
'message' => __( 'Your OTP code is incorrect. Please try again.', 'wpdef' ), |
| 558 |
) |
| 559 |
); |
| 560 |
} |
| 561 |
} |
| 562 |
|
| 563 |
/** |
| 564 |
* Triggers ONLY when a user is viewing their own profile page. |
| 565 |
* For all users need to use the hook 'edit_user_profile_update'. |
| 566 |
* |
| 567 |
* @param int $user_id |
| 568 |
*/ |
| 569 |
public function profile_update( $user_id ) { |
| 570 |
if ( isset( $_POST['_wpdef_2fa_nonce_user_options'] ) ) { |
| 571 |
check_admin_referer( 'wpdef_2fa_user_options', '_wpdef_2fa_nonce_user_options' ); |
| 572 |
|
| 573 |
if ( |
| 574 |
! isset( $_POST[ Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY ] ) |
| 575 |
|| ! is_array( $_POST[ Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY ] ) |
| 576 |
) { |
| 577 |
return; |
| 578 |
} |
| 579 |
// Remove empty elements. |
| 580 |
$checked_providers = array_diff( $_POST[ Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY ], array( '' ) ); |
| 581 |
// If no option is checked then the values for default provider and enabled providers are cleared. |
| 582 |
if ( empty( $checked_providers ) ) { |
| 583 |
update_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, '' ); |
| 584 |
update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, '' ); |
| 585 |
|
| 586 |
return; |
| 587 |
} |
| 588 |
|
| 589 |
$providers = $this->service->get_providers(); |
| 590 |
// For Fallback-Email method: the email value should be not empty and valid. |
| 591 |
if ( in_array( Fallback_Email::$slug, $checked_providers, true ) ) { |
| 592 |
$email = HTTP::post( 'def_2fa_backup_email' ); |
| 593 |
if ( ! empty( $email ) && filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { |
| 594 |
update_user_meta( $user_id, Fallback_Email::FALLBACK_EMAIL_KEY, $email ); |
| 595 |
} else { |
| 596 |
unset( $checked_providers[ Fallback_Email::$slug ] ); |
| 597 |
} |
| 598 |
} |
| 599 |
|
| 600 |
// For Webauthn method: A user must have at least once device registered. |
| 601 |
$key = array_search( Webauthn::$slug, $checked_providers ); |
| 602 |
if ( false !== $key ) { |
| 603 |
$user_authenticators = wd_di()->get( \WP_Defender\Controller\Webauthn::class )->get_current_user_authenticators( $user_id ); |
| 604 |
if ( 0 === count( $user_authenticators ) ) { |
| 605 |
unset( $checked_providers[ $key ] ); |
| 606 |
} |
| 607 |
} |
| 608 |
|
| 609 |
// Current user. |
| 610 |
$user = get_user_by( 'id', $user_id ); |
| 611 |
// Enable only the available providers. |
| 612 |
$enabled_providers = array(); |
| 613 |
foreach ( $providers as $slug => $provider ) { |
| 614 |
if ( in_array( $slug, $checked_providers, true ) && $provider->is_available_for_user( $user ) ) { |
| 615 |
$enabled_providers[] = $slug; |
| 616 |
} |
| 617 |
} |
| 618 |
update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers ); |
| 619 |
// Default provider must be enabled. |
| 620 |
$default_provider = $_POST[ Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY ] ?? ''; |
| 621 |
if ( ! empty( $default_provider ) && in_array( $default_provider, $checked_providers, true ) ) { |
| 622 |
update_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, $default_provider ); |
| 623 |
} |
| 624 |
} |
| 625 |
} |
| 626 |
|
| 627 |
/** |
| 628 |
* Check if DEFENDER_DEBUG is enabled for the route. |
| 629 |
* |
| 630 |
* @param string $route |
| 631 |
* |
| 632 |
* @return string |
| 633 |
*/ |
| 634 |
public function check_route( $route ) { |
| 635 |
|
| 636 |
return defined( 'DEFENDER_DEBUG' ) && true === constant( 'DEFENDER_DEBUG' ) ? wp_slash( $route ) : $route; |
| 637 |
} |
| 638 |
|
| 639 |
/** |
| 640 |
* A simple filter to show activate 2fa screen on profile page. |
| 641 |
* |
| 642 |
* @param object $user \WP_User object |
| 643 |
* |
| 644 |
* @return void |
| 645 |
*/ |
| 646 |
public function show_user_profile( $user ) { |
| 647 |
if ( empty( $user->roles ) ) { |
| 648 |
return; |
| 649 |
} |
| 650 |
// This method is better than is_intersected_arrays() because it is flexibly controlled with a nested hook. |
| 651 |
if ( ! $this->is_auth_enabled_for_user_role( $user ) ) { |
| 652 |
return; |
| 653 |
} |
| 654 |
wp_enqueue_style( 'defender-profile-2fa', defender_asset_url( '/assets/css/two-factor.css' ) ); |
| 655 |
|
| 656 |
$webauthn_controller = wd_di()->get( \WP_Defender\Controller\Webauthn::class ); |
| 657 |
$webauthn_requirements = $this->check_webauthn_requirements(); |
| 658 |
if ( $this->service->is_checked_enabled_provider_by_slug( $user, Webauthn::$slug ) && ! $webauthn_requirements ) { |
| 659 |
$this->service->remove_enabled_provider_for_user( Webauthn::$slug, $user ); |
| 660 |
} |
| 661 |
|
| 662 |
wp_enqueue_script( |
| 663 |
'wpdef_webauthn_common_script', |
| 664 |
plugins_url( 'assets/js/webauthn-common.js', WP_DEFENDER_FILE ), |
| 665 |
array(), |
| 666 |
DEFENDER_VERSION, |
| 667 |
true |
| 668 |
); |
| 669 |
wp_enqueue_script( |
| 670 |
'wpdef_webauthn_script', |
| 671 |
plugins_url( 'assets/js/webauthn.js', WP_DEFENDER_FILE ), |
| 672 |
array( |
| 673 |
'jquery', |
| 674 |
'wpdef_webauthn_common_script', |
| 675 |
), |
| 676 |
DEFENDER_VERSION, |
| 677 |
true |
| 678 |
); |
| 679 |
$user = wp_get_current_user(); |
| 680 |
wp_localize_script( |
| 681 |
'wpdef_webauthn_script', |
| 682 |
'webauthn', |
| 683 |
array( |
| 684 |
'admin_url' => admin_url( 'admin-ajax.php' ), |
| 685 |
'nonce' => wp_create_nonce( 'wpdef_webauthn' ), |
| 686 |
'i18n' => $webauthn_controller->get_translations(), |
| 687 |
'registered_auths' => $webauthn_controller->get_current_user_authenticators( $user->ID ), |
| 688 |
'username' => ! empty( $user->user_login ) ? $user->user_login : '', |
| 689 |
) |
| 690 |
); |
| 691 |
|
| 692 |
$forced_auth = $this->service->is_intersected_arrays( $user->roles, $this->model->force_auth_roles ); |
| 693 |
$default_values = $this->model->get_default_values(); |
| 694 |
$enabled_providers = $this->service->get_available_providers_for_user( $user ); |
| 695 |
$enabled_provider_slugs = ! empty( $enabled_providers ) ? array_keys( $enabled_providers ) : array(); |
| 696 |
$default_provider_slug = $this->service->get_default_provider_slug_for_user( $user->ID ); |
| 697 |
$webauthn_enabled = $this->service->is_checked_enabled_provider_by_slug( $user, Webauthn::$slug ); |
| 698 |
|
| 699 |
$this->render_partial( |
| 700 |
'two-fa/user-options', |
| 701 |
array( |
| 702 |
'is_force_auth' => $forced_auth && $this->model->force_auth && empty( $enabled_providers ), |
| 703 |
'force_auth_message' => $this->model->force_auth_mess, |
| 704 |
'default_message' => $default_values['message'], |
| 705 |
'user' => $user, |
| 706 |
'all_providers' => $this->service->get_providers(), |
| 707 |
'enabled_providers_key' => Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, |
| 708 |
'default_provider_key' => Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, |
| 709 |
'checked_provider_slugs' => $enabled_provider_slugs, |
| 710 |
'checked_def_provider_slug' => ! empty( $default_provider_slug ) ? $default_provider_slug : null, |
| 711 |
'webauthn_requirements' => $webauthn_requirements, |
| 712 |
'webauthn_enabled' => $webauthn_enabled, |
| 713 |
'webauthn_slug' => Webauthn::$slug, |
| 714 |
) |
| 715 |
); |
| 716 |
} |
| 717 |
|
| 718 |
/** |
| 719 |
* Save settings. |
| 720 |
* |
| 721 |
* @param Request $request |
| 722 |
* |
| 723 |
* @return Response |
| 724 |
* @defender_route |
| 725 |
*/ |
| 726 |
public function save_settings( Request $request ) { |
| 727 |
$model = $this->model; |
| 728 |
$data = $request->get_data(); |
| 729 |
$model->import( $data ); |
| 730 |
if ( $model->validate() ) { |
| 731 |
$model->save(); |
| 732 |
Config_Hub_Helper::set_clear_active_flag(); |
| 733 |
|
| 734 |
return new Response( |
| 735 |
true, |
| 736 |
array_merge( |
| 737 |
array( |
| 738 |
'message' => __( 'Your settings have been updated.', 'wpdef' ), |
| 739 |
), |
| 740 |
$this->data_frontend() |
| 741 |
) |
| 742 |
); |
| 743 |
} |
| 744 |
|
| 745 |
return new Response( |
| 746 |
false, |
| 747 |
array( |
| 748 |
'message' => $model->get_formatted_errors(), |
| 749 |
) |
| 750 |
); |
| 751 |
} |
| 752 |
|
| 753 |
/** |
| 754 |
* @throws \ReflectionException |
| 755 |
*/ |
| 756 |
public function enqueue_assets() { |
| 757 |
if ( ! $this->is_page_active() ) { |
| 758 |
return; |
| 759 |
} |
| 760 |
wp_enqueue_script( 'clipboard' ); |
| 761 |
wp_enqueue_media(); |
| 762 |
wp_localize_script( 'def-2fa', 'two_fa', $this->data_frontend() ); |
| 763 |
wp_enqueue_script( 'def-2fa' ); |
| 764 |
$this->enqueue_main_assets(); |
| 765 |
} |
| 766 |
|
| 767 |
/** |
| 768 |
* Send test email, use in settings screen. |
| 769 |
* |
| 770 |
* @param Request $request |
| 771 |
* |
| 772 |
* @return Response |
| 773 |
* @defender_route |
| 774 |
*/ |
| 775 |
public function send_test_email( Request $request ) { |
| 776 |
$data = $request->get_data( |
| 777 |
array( |
| 778 |
'email_subject' => array( |
| 779 |
'type' => 'string', |
| 780 |
'sanitize' => 'sanitize_text_field', |
| 781 |
), |
| 782 |
'email_sender' => array( |
| 783 |
'type' => 'string', |
| 784 |
'sanitize' => 'sanitize_text_field', |
| 785 |
), |
| 786 |
'email_body' => array( |
| 787 |
'type' => 'string', |
| 788 |
'sanitize' => 'wp_kses_post', |
| 789 |
), |
| 790 |
) |
| 791 |
); |
| 792 |
|
| 793 |
$subject = $data['email_subject']; |
| 794 |
$sender = $data['email_sender']; |
| 795 |
$body = $this->render_partial( |
| 796 |
'email/2fa-lost-phone', |
| 797 |
array( |
| 798 |
'body' => nl2br( $data['email_body'] ), |
| 799 |
), |
| 800 |
false |
| 801 |
); |
| 802 |
|
| 803 |
$params = array( |
| 804 |
'passcode' => '[a-sample-passcode]', |
| 805 |
'display_name' => $this->get_user_display( get_current_user_id() ), |
| 806 |
); |
| 807 |
|
| 808 |
foreach ( $params as $key => $param ) { |
| 809 |
$body = str_replace( "{{{$key}}}", $param, $body ); |
| 810 |
} |
| 811 |
$headers = array( 'Content-Type: text/html; charset=UTF-8' ); |
| 812 |
if ( $sender ) { |
| 813 |
$from_email = get_bloginfo( 'admin_email' ); |
| 814 |
$headers[] = sprintf( 'From: %s <%s>', $sender, $from_email ); |
| 815 |
} |
| 816 |
// Main email template. |
| 817 |
$body = $this->render_partial( |
| 818 |
'email/index', |
| 819 |
array( |
| 820 |
'title' => __( 'Two-Factor Authentication', 'wpdef' ), |
| 821 |
'content_body' => $body, |
| 822 |
), |
| 823 |
false |
| 824 |
); |
| 825 |
|
| 826 |
$send_mail = wp_mail( Fallback_Email::get_backup_email(), $subject, $body, $headers ); |
| 827 |
if ( $send_mail ) { |
| 828 |
return new Response( |
| 829 |
true, |
| 830 |
array( |
| 831 |
'message' => __( 'Test email has been sent to your email.', 'wpdef' ), |
| 832 |
) |
| 833 |
); |
| 834 |
} else { |
| 835 |
return new Response( |
| 836 |
false, |
| 837 |
array( |
| 838 |
'message' => __( 'Test email failed.', 'wpdef' ), |
| 839 |
) |
| 840 |
); |
| 841 |
} |
| 842 |
} |
| 843 |
|
| 844 |
/** |
| 845 |
* @return array |
| 846 |
*/ |
| 847 |
public function to_array() { |
| 848 |
$settings = new Two_Fa(); |
| 849 |
[$routes, $nonces] = Route::export_routes( 'two_fa' ); |
| 850 |
|
| 851 |
return array( |
| 852 |
'enabled' => $settings->enabled, |
| 853 |
'useable' => $settings->enabled && count( $settings->user_roles ), |
| 854 |
'nonces' => $nonces, |
| 855 |
'endpoints' => $routes, |
| 856 |
); |
| 857 |
} |
| 858 |
|
| 859 |
public function main_view() { |
| 860 |
$this->render( 'main' ); |
| 861 |
} |
| 862 |
|
| 863 |
public function remove_settings() { |
| 864 |
( new Two_Fa() )->delete(); |
| 865 |
} |
| 866 |
|
| 867 |
/* |
| 868 |
* Remove all users meta. Keys need to remove: |
| 869 |
* defenderAuthEmail, defenderAuthOn, defenderForceAuth, defenderBackupCode, defenderAuthSecret, |
| 870 |
* Two_Fa::DEFAULT_PROVIDER_USER_KEY, Two_Fa::ENABLED_PROVIDERS_USER_KEY, |
| 871 |
* Backup_Codes::BACKUP_CODE_VALUES, Backup_Codes::BACKUP_CODE_START. |
| 872 |
*/ |
| 873 |
public function remove_data() { |
| 874 |
global $wpdb; |
| 875 |
$sql = "DELETE FROM {$wpdb->usermeta} WHERE meta_key IN ('defenderAuthEmail','defenderAuthOn','defenderForceAuth','defenderBackupCode','defenderAuthSecret', 'wd_2fa_default_provider', 'wd_2fa_enabled_providers', 'wd_2fa_backup_codes', 'wd_2fa_backup_codes_is_activated');"; |
| 876 |
$wpdb->query( $sql ); |
| 877 |
} |
| 878 |
|
| 879 |
/** |
| 880 |
* Filter users by 2FA option. |
| 881 |
*/ |
| 882 |
public function filter_users_by_2fa( $query ) { |
| 883 |
global $pagenow; |
| 884 |
|
| 885 |
if ( is_admin() |
| 886 |
&& 'users.php' === $pagenow |
| 887 |
&& isset( $_GET['wpdef_two_fa'] ) |
| 888 |
&& 'enabled' === sanitize_text_field( $_GET['wpdef_two_fa'] ) |
| 889 |
) { |
| 890 |
$query->set( |
| 891 |
'meta_query', |
| 892 |
array( |
| 893 |
array( |
| 894 |
'key' => Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, |
| 895 |
'value' => array_keys( $this->service->get_providers() ), |
| 896 |
'compare' => 'IN', |
| 897 |
), |
| 898 |
) |
| 899 |
); |
| 900 |
} |
| 901 |
} |
| 902 |
|
| 903 |
/** |
| 904 |
* All the variables that we will show on frontend, both in the main page, or dashboard widget. |
| 905 |
* |
| 906 |
* @return array |
| 907 |
*/ |
| 908 |
public function data_frontend() { |
| 909 |
return array_merge( |
| 910 |
array( |
| 911 |
'model' => $this->model->export(), |
| 912 |
'all_roles' => wp_list_pluck( get_editable_roles(), 'name' ), |
| 913 |
'count' => $this->service->count_users_with_enabled_2fa(), |
| 914 |
'notices' => $this->compatibility_notices, |
| 915 |
'new_feature' => '<span class="sui-tag sui-tag-beta margin-right-10">' . __( 'Beta', 'wpdef' ) . '</span>' . sprintf( |
| 916 |
/* translators: %s: link */ |
| 917 |
__( 'Biometric authentication is now available. <a target="_blank" href="%s">Click here</a> to find out more.', 'wpdef' ), |
| 918 |
'https://wpmudev.com/docs/wpmu-dev-plugins/defender/#biometric' |
| 919 |
) |
| 920 |
), |
| 921 |
$this->dump_routes_and_nonces() |
| 922 |
); |
| 923 |
} |
| 924 |
|
| 925 |
/** |
| 926 |
* @param array $data |
| 927 |
*/ |
| 928 |
public function import_data( $data ) { |
| 929 |
$model = new Two_Fa(); |
| 930 |
|
| 931 |
$model->import( $data ); |
| 932 |
/** |
| 933 |
* Sometime, the custom image broken when import, when that happen, we will fallback |
| 934 |
* into default image. |
| 935 |
*/ |
| 936 |
$model->custom_graphic_url = $this->service->get_custom_graphic_url( $model->custom_graphic_url ); |
| 937 |
if ( $model->validate() ) { |
| 938 |
$model->save(); |
| 939 |
} |
| 940 |
} |
| 941 |
|
| 942 |
/** |
| 943 |
* @return array |
| 944 |
*/ |
| 945 |
public function export_strings() { |
| 946 |
$settings = new Two_Fa(); |
| 947 |
|
| 948 |
return array( |
| 949 |
$settings->enabled ? __( 'Active', 'wpdef' ) : __( 'Inactive', 'wpdef' ), |
| 950 |
); |
| 951 |
} |
| 952 |
|
| 953 |
/** |
| 954 |
* @param array $config |
| 955 |
* @param bool $is_pro |
| 956 |
* |
| 957 |
* @return array |
| 958 |
*/ |
| 959 |
public function config_strings( $config, $is_pro ) { |
| 960 |
|
| 961 |
return array( |
| 962 |
$config['enabled'] ? __( 'Active', 'wpdef' ) : __( 'Inactive', 'wpdef' ), |
| 963 |
); |
| 964 |
} |
| 965 |
|
| 966 |
/** |
| 967 |
* Stop ajax login on membership 2. |
| 968 |
* |
| 969 |
* @return bool |
| 970 |
*/ |
| 971 |
public function m2_no_ajax() { |
| 972 |
return false; |
| 973 |
} |
| 974 |
|
| 975 |
/** |
| 976 |
* WooCommerce prevents any user who cannot 'edit_posts' (subscribers, customers etc.) from accessing admin. |
| 977 |
* Here we are disabling WooCommerce default behavior, if force 2FA is enabled. |
| 978 |
* |
| 979 |
* @param bool $prevent Prevent admin access. |
| 980 |
*/ |
| 981 |
public function handle_woocommerce_prevent_admin_access( $prevent ) { |
| 982 |
$user = wp_get_current_user(); |
| 983 |
if ( ! is_object( $user ) ) { |
| 984 |
return; |
| 985 |
} |
| 986 |
// Is User role from common list checked? |
| 987 |
if ( false === $this->is_auth_enabled_for_user_role( $user ) ) { |
| 988 |
return $prevent; |
| 989 |
} |
| 990 |
// Is 'Force Authentication' checked? |
| 991 |
if ( false === $this->model->force_auth ) { |
| 992 |
return $prevent; |
| 993 |
} |
| 994 |
// Is User role from forced list checked? |
| 995 |
if ( $this->service->is_force_auth_enable_for( $user->ID, $this->model->force_auth_roles ) ) { |
| 996 |
return false; |
| 997 |
} |
| 998 |
// Is TOTP saved with a passcode? |
| 999 |
if ( ! empty( $this->service->get_available_providers_for_user( $user ) ) ) { |
| 1000 |
return $prevent; |
| 1001 |
} |
| 1002 |
|
| 1003 |
return $prevent; |
| 1004 |
} |
| 1005 |
|
| 1006 |
/** |
| 1007 |
* WooCommerce specific hooks. |
| 1008 |
* |
| 1009 |
* @return void |
| 1010 |
*/ |
| 1011 |
private function woocommerce_hooks() { |
| 1012 |
// This filter added only for disable WooCommerce default behavior. |
| 1013 |
add_filter( 'woocommerce_prevent_admin_access', array( $this, 'handle_woocommerce_prevent_admin_access' ), 10, 1 ); |
| 1014 |
|
| 1015 |
// Handle WooCommerce MyAccount page login redirect. |
| 1016 |
add_filter( 'woocommerce_login_redirect', array( $this, 'handle_woocommerce_login_redirect' ), 10, 2 ); |
| 1017 |
|
| 1018 |
add_action( 'woocommerce_login_form_end', array( $this, 'add_redirect_to_input' ) ); |
| 1019 |
} |
| 1020 |
|
| 1021 |
/** |
| 1022 |
* WooCommerce by default redirect users to My-account page. |
| 1023 |
* Here we are checking force 2FA is enabled or not. |
| 1024 |
* |
| 1025 |
* @param string $redirect Redirect URL. |
| 1026 |
* @param \WP_User $user Logged-in user. |
| 1027 |
* |
| 1028 |
* @return void |
| 1029 |
*/ |
| 1030 |
public function handle_woocommerce_login_redirect( $redirect, $user ) { |
| 1031 |
// Is User role from common list checked? |
| 1032 |
if ( false === $this->is_auth_enabled_for_user_role( $user ) ) { |
| 1033 |
return $redirect; |
| 1034 |
} |
| 1035 |
// Is 'Force Authentication' checked? |
| 1036 |
if ( false === $this->model->force_auth ) { |
| 1037 |
return $redirect; |
| 1038 |
} |
| 1039 |
// Is User role from forced list checked? |
| 1040 |
if ( ! $this->service->is_force_auth_enable_for( $user->ID, $this->model->force_auth_roles ) ) { |
| 1041 |
return $redirect; |
| 1042 |
} |
| 1043 |
// Is TOTP saved with a passcode? |
| 1044 |
if ( empty( $this->service->get_available_providers_for_user( $user ) ) ) { |
| 1045 |
return admin_url( 'profile.php' ) . '#defender-security'; |
| 1046 |
} |
| 1047 |
|
| 1048 |
return $redirect; |
| 1049 |
} |
| 1050 |
|
| 1051 |
/** |
| 1052 |
* Finds whether at least anyone user role in enabled 2FA user roles array. |
| 1053 |
* |
| 1054 |
* @param \WP_User $user User instance object. |
| 1055 |
* |
| 1056 |
* @return bool Return true for at least one role matches else false return. |
| 1057 |
*/ |
| 1058 |
private function is_auth_enabled_for_user_role( \WP_User $user ) { |
| 1059 |
/** |
| 1060 |
* Filter 2FA option for a specific user. |
| 1061 |
* |
| 1062 |
* @param bool $flag |
| 1063 |
* @param string $user_id |
| 1064 |
*/ |
| 1065 |
if ( false === apply_filters( 'wp_defender_2fa_user_enabled', true, $user->ID ) ) { |
| 1066 |
return false; |
| 1067 |
} |
| 1068 |
return ! empty( array_intersect( $user->roles, $this->model->user_roles ) ); |
| 1069 |
} |
| 1070 |
|
| 1071 |
/** |
| 1072 |
* Return redirect URL after 2FA submit. |
| 1073 |
*/ |
| 1074 |
private function redirect_url() { |
| 1075 |
return HTTP::post( 'redirect_to', defender_get_request_url() ); |
| 1076 |
} |
| 1077 |
|
| 1078 |
/** |
| 1079 |
* Adds redirect_to hidden input to Woo login. |
| 1080 |
*/ |
| 1081 |
public function add_redirect_to_input() { |
| 1082 |
echo '<input type="hidden" name="redirect_to" value="' . defender_get_request_url() . '">'; |
| 1083 |
} |
| 1084 |
|
| 1085 |
/** |
| 1086 |
* Generate Backup codes on Profile page. |
| 1087 |
* |
| 1088 |
* @return Response |
| 1089 |
* @defender_route |
| 1090 |
* @is_public |
| 1091 |
*/ |
| 1092 |
public function generate_backup_codes() { |
| 1093 |
$user = wp_get_current_user(); |
| 1094 |
|
| 1095 |
return new Response( |
| 1096 |
true, |
| 1097 |
array( |
| 1098 |
'codes' => Backup_Codes::generate_codes( $user ), |
| 1099 |
'count' => Backup_Codes::display_number_of_codes( Backup_Codes::get_unused_codes_for_user( $user ) ), |
| 1100 |
'title' => sprintf( |
| 1101 |
/* translators: %s: count */ |
| 1102 |
__( 'Defender Backup Codes for %s:', 'wpdef' ), |
| 1103 |
network_site_url() |
| 1104 |
), |
| 1105 |
'button_text' => __( 'Get New Codes', 'wpdef' ), |
| 1106 |
'description' => __( 'Each backup code can only be used to log in once.', 'wpdef' ), |
| 1107 |
) |
| 1108 |
); |
| 1109 |
} |
| 1110 |
} |
| 1111 |
|