PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / controller / class-webauthn.php

class-webauthn.php in Defender Security – Malware Scanner, Login Security & Firewall 5.7.2, at src/controller/class-webauthn.php

714 lines 23.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles Web auth related functionality.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use Error;
11 use Exception;
12 use Webauthn\Server;
13 use WP_Defender\Controller;
14 use WP_Defender\Behavior\WPMUDEV;
15 use Nyholm\Psr7\Factory\Psr17Factory;
16 use Webauthn\PublicKeyCredentialRpEntity;
17 use Nyholm\Psr7Server\ServerRequestCreator;
18 use Webauthn\PublicKeyCredentialUserEntity;
19 use Webauthn\AuthenticatorSelectionCriteria;
20 use Webauthn\PublicKeyCredentialRequestOptions;
21 use Webauthn\PublicKeyCredentialCreationOptions;
22 use WP_Defender\Traits\Webauthn as Webauthn_Trait;
23 use WP_Defender\Component\Two_Fa as Two_Fa_Component;
24 use WP_Defender\Component\Webauthn as Webauthn_Component;
25 use WP_Defender\Controller\Two_Factor as Two_Fa_Controller;
26 use WP_Defender\Component\Two_Factor\Providers\Webauthn as Webauthn_Provider;
27
28 /**
29 * Handles Web auth related functionality.
30 *
31 * @since 3.0.0
32 */
33 class Webauthn extends Controller {
34
35 use Webauthn_Trait;
36
37 /**
38 * Service for handling logic.
39 *
40 * @var Webauthn_Component
41 */
42 protected $service;
43
44 public const ALLOWED_AUTH_TYPES = array( 'platform', 'cross-platform' );
45
46 /**
47 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
48 */
49 public function __construct() {
50 $this->service = wd_di()->get( Webauthn_Component::class );
51
52 if ( $this->service->check_webauthn_requirements() ) {
53 // Register a new authenticator.
54 add_action( 'wp_ajax_defender_webauthn_create_challenge', array( $this, 'create_challenge' ) );
55 add_action( 'wp_ajax_defender_webauthn_verify_challenge', array( $this, 'verify_challenge' ) );
56 // Remove authenticator.
57 add_action( 'wp_ajax_defender_webauthn_remove_authenticator', array( $this, 'remove_authenticator' ) );
58 // Rename authenticator.
59 add_action( 'wp_ajax_defender_webauthn_rename_authenticator', array( $this, 'rename_authenticator' ) );
60 // Verify user's device.
61 add_action( 'wp_ajax_defender_webauthn_get_option', array( $this, 'get_credential_request_option' ) );
62 add_action(
63 'wp_ajax_nopriv_defender_webauthn_get_option',
64 array(
65 $this,
66 'get_credential_request_option',
67 )
68 );
69 add_action( 'wp_ajax_defender_webauthn_verify_response', array( $this, 'verify_response' ) );
70 add_action( 'wp_ajax_nopriv_defender_webauthn_verify_response', array( $this, 'verify_response' ) );
71 // Handling requests in the frontend.
72 if ( wd_di()->get( Two_Fa_Controller::class )->woo_integration_enabled() ) {
73 add_action( 'wp_ajax_nopriv_defender_webauthn_create_challenge', array( $this, 'create_challenge' ) );
74 add_action( 'wp_ajax_nopriv_defender_webauthn_verify_challenge', array( $this, 'verify_challenge' ) );
75 add_action(
76 'wp_ajax_nopriv_defender_webauthn_remove_authenticator',
77 array(
78 $this,
79 'remove_authenticator',
80 )
81 );
82 add_action(
83 'wp_ajax_nopriv_defender_webauthn_rename_authenticator',
84 array(
85 $this,
86 'rename_authenticator',
87 )
88 );
89 }
90
91 // Disable userHandle mismatch notice.
92 add_action(
93 'wp_ajax_defender_webauthn_disable_user_handle_match_failed_notice',
94 array( $this, 'disable_user_handle_match_failed_notice' )
95 );
96 }
97 }
98
99 /**
100 * Get authenticator records for current user.
101 *
102 * @return array
103 */
104 public function get_current_user_authenticators(): array {
105 $arr = array();
106 $user_id = get_current_user_id();
107 $user_credentials = $this->service->getCredentials( $user_id );
108 if ( array() !== $user_credentials ) {
109 foreach ( $user_credentials as $key => $value ) {
110 $arr[] = array(
111 'key' => $this->base64url_encode( $key ),
112 'label' => $value['label'],
113 'added' => wp_date( 'Y-m-d', $value['added'] ),
114 'auth_type' => $value['authenticator_type'],
115 );
116 }
117 }
118
119 return $arr;
120 }
121
122 /**
123 * Get user entity.
124 *
125 * @param int $user_id User ID.
126 *
127 * @return false|PublicKeyCredentialUserEntity
128 */
129 public function get_user_entity( int $user_id ) {
130 if ( $user_id <= 0 ) {
131 return false;
132 }
133
134 $user = get_user_by( 'id', $user_id );
135 if ( ! is_object( $user ) ) {
136 return false;
137 }
138
139 $user_hash = $this->get_user_hash( $user->user_login );
140 $user_avatar = get_avatar_url( $user->user_email, array( 'scheme' => 'https' ) );
141
142 return new PublicKeyCredentialUserEntity(
143 $user->user_login,
144 $user_hash,
145 $user->display_name,
146 $user_avatar
147 );
148 }
149
150 /**
151 * Create challenge.
152 *
153 * @return void
154 * @throws Exception If something goes wrong.
155 */
156 public function create_challenge(): void {
157 try {
158 if ( ! $this->verify_nonce( 'wpdef_webauthn' ) ) {
159 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
160 }
161
162 $type = defender_get_data_from_request( 'type', 'g' );
163 if ( '' === $type ) {
164 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
165 }
166
167 $user_id = get_current_user_id();
168 $user_entity = $this->get_user_entity( $user_id );
169 if ( false === $user_entity ) {
170 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
171 }
172
173 // Get the list of all authenticators associated to a user.
174 $credential_sources = $this->service->findAllForUserEntity( $user_entity );
175
176 // Convert the Credential Sources into Public Key Credential Descriptors.
177 $exclude_credentials = array_map(
178 function ( $credential ) {
179 return $credential->getPublicKeyCredentialDescriptor();
180 },
181 $credential_sources
182 );
183
184 if ( 'platform' === $type ) {
185 $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM;
186 } elseif ( 'cross-platform' === $type ) {
187 $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
188 } else {
189 $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE;
190 }
191
192 // Create authenticator selection.
193 $authenticator_selection_criteria = new AuthenticatorSelectionCriteria();
194 $authenticator_selection_criteria->setAuthenticatorAttachment( $authenticator_type );
195 $authenticator_selection_criteria->setRequireResidentKey( false );
196 $authenticator_selection_criteria->setUserVerification( AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED );
197
198 $rp_entity = new PublicKeyCredentialRpEntity(
199 $this->get_site_name(),
200 $this->get_site_domain()
201 );
202 $server = new Server(
203 $rp_entity,
204 $this->service,
205 null
206 );
207
208 $public_key_credential_creation_options = $server->generatePublicKeyCredentialCreationOptions(
209 $user_entity,
210 PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
211 $exclude_credentials,
212 $authenticator_selection_criteria
213 );
214
215 $client_id = time() . defender_generate_random_string( 24 );
216
217 // Set transition for later use.
218 $encoded = wp_json_encode( $public_key_credential_creation_options );
219 // This is not obfuscation. Just encode the resulting string.
220 $this->set_trans_val( 'pub_key_cco', base64_encode( $encoded ), $client_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
221
222 // Send Challenge.
223 $public_key_credential_creation_options = json_decode( $encoded, true );
224 $public_key_credential_creation_options['clientID'] = $client_id;
225 wp_send_json_success( $public_key_credential_creation_options );
226 } catch ( Error $error ) {
227 wp_send_json_error( $error->getMessage() );
228 } catch ( Exception $exception ) {
229 wp_send_json_error( $exception->getMessage() );
230 }
231 }
232
233 /**
234 * Verify challenge.
235 *
236 * @return void
237 * @throws Exception If something goes wrong.
238 */
239 public function verify_challenge(): void {
240 $client_id = null;
241 try {
242 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
243 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
244 }
245
246 $posted_data = defender_get_data_from_request( null, 'p' );
247 if ( '' === $posted_data['data'] || '' === $posted_data['client_id'] ) {
248 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
249 }
250
251 $psr17_factory = new Psr17Factory();
252 $creator = new ServerRequestCreator(
253 $psr17_factory,
254 $psr17_factory,
255 $psr17_factory,
256 $psr17_factory
257 );
258
259 $server_request = $creator->fromGlobals();
260
261 $rp_entity = new PublicKeyCredentialRpEntity(
262 $this->get_site_name(),
263 $this->get_site_domain()
264 );
265
266 $server = new Server(
267 $rp_entity,
268 $this->service,
269 null
270 );
271
272 $response_data = base64_decode( sanitize_text_field( $posted_data['data'] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
273 $client_id = sanitize_text_field( $posted_data['client_id'] );
274 $pub_key_cco = $this->get_trans_val( 'pub_key_cco', $client_id );
275 $pub_key_cco = PublicKeyCredentialCreationOptions::createFromString( base64_decode( $pub_key_cco ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
276
277 $public_key_credential_source = $server->loadAndCheckAttestationResponse(
278 $response_data,
279 $pub_key_cco,
280 $server_request
281 );
282
283 $this->service->saveCredentialSource( $public_key_credential_source );
284
285 // Delete transient.
286 $this->delete_trans( 'pub_key_cco', $client_id );
287
288 $response = array();
289 $username = $pub_key_cco->getUser()->getName();
290 $user = get_user_by( 'login', $username );
291
292 $cred_data = array();
293 if ( is_object( $user ) ) {
294 $cred_data = $this->service->getCredentials( $user->ID );
295 }
296
297 $cred_id = base64_encode( $public_key_credential_source->getPublicKeyCredentialId() ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
298 if ( isset( $cred_data[ $cred_id ] ) ) {
299 $response = array(
300 'key' => $this->base64url_encode( $cred_id ),
301 'label' => ucfirst( $cred_data[ $cred_id ]['label'] ),
302 'added' => wp_date( 'Y-m-d', $cred_data[ $cred_id ]['added'] ),
303 'auth_type' => $cred_data[ $cred_id ]['authenticator_type'],
304 );
305 }
306 wp_send_json_success( $response );
307 } catch ( Error $error ) {
308 $this->delete_trans( 'pub_key_cco', $client_id );
309 wp_send_json_error( $error->getMessage() );
310 } catch ( Exception $exception ) {
311 $this->delete_trans( 'pub_key_cco', $client_id );
312 wp_send_json_error( $exception->getMessage() );
313 }
314 }
315
316 /**
317 * Remove authenticator.
318 *
319 * @return void
320 * @throws Exception If something goes wrong.
321 */
322 public function remove_authenticator(): void {
323 try {
324 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
325 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
326 }
327
328 $cred_id = defender_get_data_from_request( 'key', 'p' );
329 if ( '' === $cred_id ) {
330 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
331 }
332
333 $cred_id = $this->base64url_decode( $cred_id );
334 $user_id = get_current_user_id();
335 $option_user_credentials = $this->service->getCredentials( $user_id );
336
337 if ( isset( $option_user_credentials[ $cred_id ] ) ) {
338 unset( $option_user_credentials[ $cred_id ] );
339 $this->service->setCredentials( $user_id, $option_user_credentials );
340 $this->service->removeUserHandleMatchFailed( $user_id, $cred_id );
341
342 if ( 0 === count( $option_user_credentials ) ) {
343 $enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true );
344 if ( ! is_array( $enabled_providers ) ) {
345 $enabled_providers = array();
346 }
347 $key = array_search( Webauthn_Provider::$slug, $enabled_providers, true );
348 if ( false !== $key ) {
349 unset( $enabled_providers[ $key ] );
350 update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers );
351 }
352 }
353
354 wp_send_json_success( esc_html__( 'Authenticator removed.', 'defender-security' ) );
355 }
356
357 wp_send_json_error( esc_html__( 'Key did not match any registered authenticator.', 'defender-security' ) );
358 } catch ( Error $error ) {
359 wp_send_json_error( $error->getMessage() );
360 } catch ( Exception $exception ) {
361 wp_send_json_error( $exception->getMessage() );
362 }
363 }
364
365 /**
366 * Rename authenticator.
367 *
368 * @return void
369 * @throws Exception If something goes wrong.
370 */
371 public function rename_authenticator(): void {
372 try {
373 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
374 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
375 }
376
377 $posted_data = defender_get_data_from_request( null, 'p' );
378 if ( ! isset( $posted_data['key'], $posted_data['label'] ) || '' === $posted_data['key'] || '' === $posted_data['label'] ) {
379 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
380 }
381
382 $cred_id = sanitize_text_field( $posted_data['key'] );
383 $cred_id = $this->base64url_decode( $cred_id );
384 $new_label = sanitize_text_field( $posted_data['label'] );
385 $user_id = get_current_user_id();
386 $option_user_credentials = $this->service->getCredentials( $user_id );
387
388 if ( isset( $option_user_credentials[ $cred_id ] ) ) {
389 $option_user_credentials[ $cred_id ]['label'] = $new_label;
390 $this->service->setCredentials( $user_id, $option_user_credentials );
391
392 wp_send_json_success( esc_html__( 'Authenticator identifier renamed.', 'defender-security' ) );
393 }
394
395 wp_send_json_error( esc_html__( 'Key did not match any registered authenticator.', 'defender-security' ) );
396 } catch ( Error $error ) {
397 wp_send_json_error( $error->getMessage() );
398 } catch ( Exception $exception ) {
399 wp_send_json_error( $exception->getMessage() );
400 }
401 }
402
403 /**
404 * Get credential request option.
405 *
406 * @return void
407 * @throws Exception If something goes wrong.
408 */
409 public function get_credential_request_option(): void {
410 try {
411 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
412 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
413 }
414
415 $posted_data = defender_get_data_from_request( null, 'p' );
416 if ( ! isset( $posted_data['username'] ) || '' === $posted_data['username'] ) {
417 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
418 }
419
420 $rp_entity = new PublicKeyCredentialRpEntity(
421 $this->get_site_name(),
422 $this->get_site_domain()
423 );
424
425 $server = new Server(
426 $rp_entity,
427 $this->service,
428 null
429 );
430
431 $user_entity = false;
432 $username = sanitize_text_field( $posted_data['username'] );
433 $user = get_user_by( 'login', $username );
434 if ( is_object( $user ) ) {
435 $user_entity = $this->get_user_entity( $user->ID );
436 }
437
438 if ( false === $user_entity ) {
439 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
440 }
441
442 $auth_type = isset( $posted_data['type'] ) && '' !== $posted_data['type'] ? sanitize_text_field( $posted_data['type'] ) : null;
443 if ( in_array( $auth_type, self::ALLOWED_AUTH_TYPES, true ) ) {
444 $credential_sources = $this->service->findAllForUserByType( $user->ID, $auth_type );
445 } else {
446 $credential_sources = $this->service->findAllForUserEntity( $user_entity );
447 }
448
449 if ( ! is_array( $credential_sources ) || 0 === count( $credential_sources ) ) {
450 throw new Exception( esc_html__( 'Please register a device first to authenticate it.', 'defender-security' ), 100 );
451 }
452
453 // Convert the Credential Sources into Public Key Credential Descriptors for excluding.
454 $allowed_credentials = array_map(
455 function ( $credential ) {
456 return $credential->getPublicKeyCredentialDescriptor();
457 },
458 $credential_sources
459 );
460 $user_verification = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
461
462 $public_key_credential_request_options = $server->generatePublicKeyCredentialRequestOptions(
463 $user_verification,
464 $allowed_credentials
465 );
466
467 // Set transition for later use.
468 $encoded = wp_json_encode( $public_key_credential_request_options );
469 $client_id = time() . defender_generate_random_string( 24 );
470 // This is not obfuscation. Just encode the resulting string.
471 $this->set_trans_val( 'pub_key_cro', base64_encode( $encoded ), $client_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
472
473 $public_key_credential_request_options = json_decode( $encoded, true );
474 $public_key_credential_request_options['clientID'] = $client_id;
475 wp_send_json_success( $public_key_credential_request_options );
476 } catch ( Error $error ) {
477 wp_send_json_error( $error->getMessage() );
478 } catch ( Exception $exception ) {
479 wp_send_json_error(
480 array(
481 'message' => $exception->getMessage(),
482 'code' => $exception->getCode(),
483 )
484 );
485 }
486 }
487
488 /**
489 * Verify response.
490 *
491 * @param bool $will_return Either return array or echo json.
492 *
493 * @return array
494 * @throws Exception If something goes wrong.
495 */
496 public function verify_response( bool $will_return = false ) {
497 $client_id = null;
498 try {
499 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
500 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
501 }
502
503 $posted_data = defender_get_data_from_request( null, 'p' );
504 if (
505 ! isset( $posted_data['data'], $posted_data['username'], $posted_data['client_id'] )
506 || '' === $posted_data['data']
507 || '' === $posted_data['username']
508 || '' === $posted_data['client_id']
509 ) {
510 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
511 }
512
513 $user_entity = false;
514 $response_data = base64_decode( $posted_data['data'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
515 $username = sanitize_text_field( $posted_data['username'] );
516 $client_id = sanitize_text_field( $posted_data['client_id'] );
517 $pub_key_cro = PublicKeyCredentialRequestOptions::createFromString( base64_decode( $this->get_trans_val( 'pub_key_cro', $client_id ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
518 $user = get_user_by( 'login', $username );
519
520 if ( is_object( $user ) ) {
521 $user_entity = $this->get_user_entity( $user->ID );
522 }
523
524 if ( false === $user_entity ) {
525 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
526 }
527
528 $psr17_factory = new Psr17Factory();
529 $creator = new ServerRequestCreator(
530 $psr17_factory,
531 $psr17_factory,
532 $psr17_factory,
533 $psr17_factory
534 );
535
536 $server_request = $creator->fromGlobals();
537
538 $rp_entity = new PublicKeyCredentialRpEntity(
539 $this->get_site_name(),
540 $this->get_site_domain()
541 );
542
543 $server = new Server(
544 $rp_entity,
545 $this->service,
546 null
547 );
548
549 $server->loadAndCheckAssertionResponse(
550 $response_data,
551 $pub_key_cro,
552 $user_entity,
553 $server_request
554 );
555
556 $this->delete_trans( 'pub_key_cro', $client_id );
557
558 $data = esc_html__( 'Authenticator verified successfully.', 'defender-security' );
559
560 return defender_maybe_echo_json( $data, true, $will_return );
561 } catch ( Error $error ) {
562 $this->delete_trans( 'pub_key_cro', $client_id );
563
564 return defender_maybe_echo_json( $error->getMessage(), false, $will_return );
565 } catch ( Exception $exception ) {
566 $data = array( 'message' => $exception->getMessage() );
567
568 if ( $exception->getMessage() === 'Invalid user handle' ) {
569 $user_credentials = $this->service->getCredentials( $user->ID );
570 $decoded_response = json_decode( $response_data, true );
571
572 $data['label'] = $user_credentials[ $decoded_response['rawId'] ]['label'];
573 $data['key'] = $this->base64url_encode( $decoded_response['rawId'] );
574
575 $this->service->addUserHandleMatchFailed( $user, $decoded_response );
576 }
577
578 $this->delete_trans( 'pub_key_cro', $client_id );
579
580 return defender_maybe_echo_json( $data, false, $will_return );
581 }
582 }
583
584 /**
585 * Get translation.
586 *
587 * @return array
588 */
589 public function get_translations(): array {
590 $translations = array(
591 'registration_start' => esc_html__(
592 'Registering a new authenticator is in process.',
593 'defender-security'
594 ),
595 'authenticator_reg_success' => esc_html__(
596 'Registered new authenticator.',
597 'defender-security'
598 ),
599 'authenticator_reg_failed' => esc_html__(
600 'ERROR: Something went wrong.',
601 'defender-security'
602 ),
603 'multiple_reg_attempt' => esc_html__(
604 'Registration failed! The authenticator you are trying to register is already registered with your account.',
605 'defender-security'
606 ),
607 'authentication_start' => esc_html__( 'Authenticating', 'defender-security' ),
608 'authenticator_verification_success' => esc_html__(
609 'Authenticated device successfully.',
610 'defender-security'
611 ),
612 'authenticator_verification_failed' => esc_html__(
613 'Authentication verification failed! Please make sure that biometric functionality is configured on your phone.',
614 'defender-security'
615 ),
616 'authenticator_verification_failed_user_handle_mismatch' => sprintf(
617 /* translators: %s is the name of the authenticator. */
618 esc_html__( 'Due to some Biometric Authentication security improvements, the %s device will not work. Please delete this device and re-register it.', 'defender-security' ),
619 '<strong>%s</strong>'
620 ),
621 'remove_auth' => esc_html__(
622 'Are you sure you want to remove authenticator?',
623 'defender-security'
624 ),
625 'login_failed' => esc_html__(
626 'ERROR: Verification failed.',
627 'defender-security'
628 ),
629 'login_user_handle_match_failed' => esc_html__(
630 'ERROR: Due to some Biometric Authentication security improvements, some of your registered devices might not work. Please ask your website administrator to reset 2FA for your account. Then navigate to your profile page to check which devices need re-registration.',
631 'defender-security'
632 ),
633 'client_webauthn_notice' => esc_html__(
634 'WebAuth is not supported by your web browser. Please install an updated version, or try another browser.',
635 'defender-security'
636 ),
637 'auth_user_handle_mismatch_notice' => esc_html__(
638 'This device might not work. Please delete the device and re-register it.',
639 'defender-security'
640 ),
641 'user_handle_mismatch_main_notice' => esc_html__(
642 'Due to some biometric authentication security improvements, some of your registered devices might not work. Please click the Authenticate Device button to check which devices you need to delete and re-registered.',
643 'defender-security'
644 ),
645 );
646
647 if ( ( new WPMUDEV() )->show_support_links() ) {
648 $translations['authenticator_verification_failed'] .= defender_support_ticket_text();
649 }
650
651 return $translations;
652 }
653
654 /**
655 * Disable userHandle mismatch notice.
656 *
657 * @return void
658 * @since 3.4.0
659 */
660 public function disable_user_handle_match_failed_notice(): void {
661 $this->service->disableUserHandleMatchFailedNotice( get_current_user_id() );
662 wp_send_json_success( esc_html__( 'Notice disabled!', 'defender-security' ) );
663 }
664
665 /**
666 * Delete all the data & the cache.
667 */
668 public function remove_data(): void {
669 global $wpdb;
670 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s;", "%{$this->option_prefix}%" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
671 }
672
673 /**
674 * Exports strings.
675 *
676 * @return array An array of strings.
677 */
678 public function export_strings(): array {
679 return array();
680 }
681
682 /**
683 * Converts the current object state to an array.
684 *
685 * @return array The array representation of the object.
686 */
687 public function to_array(): array {
688 return array();
689 }
690
691 /**
692 * Imports data into the model.
693 *
694 * @param array $data Data to be imported into the model.
695 */
696 public function import_data( array $data ) {
697 }
698
699 /**
700 * Removes settings for all submodules.
701 */
702 public function remove_settings(): void {
703 }
704
705 /**
706 * Provides data for the frontend.
707 *
708 * @return array An array of data for the frontend.
709 */
710 public function data_frontend(): array {
711 return array();
712 }
713 }
714