PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.3.1
Defender Security – Malware Scanner, Login Security & Firewall v5.3.1
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.3.1, at src/controller/class-webauthn.php

712 lines 23.4 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 ( ! empty( $user_credentials ) && is_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 ( empty( $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 $resident_key = false;
194 $user_verification = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
195 $authenticator_selection_criteria = new AuthenticatorSelectionCriteria(
196 $authenticator_type,
197 $resident_key,
198 $user_verification
199 );
200
201 $rp_entity = new PublicKeyCredentialRpEntity(
202 $this->get_site_name(),
203 $this->get_site_domain()
204 );
205 $server = new Server(
206 $rp_entity,
207 $this->service,
208 null
209 );
210
211 $public_key_credential_creation_options = $server->generatePublicKeyCredentialCreationOptions(
212 $user_entity,
213 PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
214 $exclude_credentials,
215 $authenticator_selection_criteria
216 );
217
218 $client_id = time() . defender_generate_random_string( 24 );
219
220 // Set transition for later use.
221 $encoded = wp_json_encode( $public_key_credential_creation_options );
222 $this->set_trans_val( 'pub_key_cco', base64_encode( $encoded ), $client_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
223
224 // Send Challenge.
225 $public_key_credential_creation_options = json_decode( $encoded, true );
226 $public_key_credential_creation_options['clientID'] = $client_id;
227 wp_send_json_success( $public_key_credential_creation_options );
228 } catch ( Error $error ) {
229 wp_send_json_error( $error->getMessage() );
230 } catch ( Exception $exception ) {
231 wp_send_json_error( $exception->getMessage() );
232 }
233 }
234
235 /**
236 * Verify challenge.
237 *
238 * @return void
239 * @throws Exception If something goes wrong.
240 */
241 public function verify_challenge(): void {
242 $client_id = null;
243 try {
244 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
245 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
246 }
247
248 $posted_data = defender_get_data_from_request( null, 'p' );
249 if ( empty( $posted_data['data'] ) || empty( $posted_data['client_id'] ) ) {
250 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
251 }
252
253 $psr17_factory = new Psr17Factory();
254 $creator = new ServerRequestCreator(
255 $psr17_factory,
256 $psr17_factory,
257 $psr17_factory,
258 $psr17_factory
259 );
260
261 $server_request = $creator->fromGlobals();
262
263 $rp_entity = new PublicKeyCredentialRpEntity(
264 $this->get_site_name(),
265 $this->get_site_domain()
266 );
267
268 $server = new Server(
269 $rp_entity,
270 $this->service,
271 null
272 );
273
274 $response_data = base64_decode( sanitize_text_field( $posted_data['data'] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
275 $client_id = sanitize_text_field( $posted_data['client_id'] );
276 $pub_key_cco = $this->get_trans_val( 'pub_key_cco', $client_id );
277 $pub_key_cco = PublicKeyCredentialCreationOptions::createFromString( base64_decode( $pub_key_cco ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
278
279 $public_key_credential_source = $server->loadAndCheckAttestationResponse(
280 $response_data,
281 $pub_key_cco,
282 $server_request
283 );
284
285 $this->service->saveCredentialSource( $public_key_credential_source );
286
287 // Delete transient.
288 $this->delete_trans( 'pub_key_cco', $client_id );
289
290 $response = array();
291 $username = $pub_key_cco->getUser()->getName();
292 $user = get_user_by( 'login', $username );
293
294 $cred_data = array();
295 if ( is_object( $user ) ) {
296 $cred_data = $this->service->getCredentials( $user->ID );
297 }
298
299 $cred_id = base64_encode( $public_key_credential_source->getPublicKeyCredentialId() ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
300 if ( isset( $cred_data[ $cred_id ] ) ) {
301 $response = array(
302 'key' => $this->base64url_encode( $cred_id ),
303 'label' => ucfirst( $cred_data[ $cred_id ]['label'] ),
304 'added' => wp_date( 'Y-m-d', $cred_data[ $cred_id ]['added'] ),
305 'auth_type' => $cred_data[ $cred_id ]['authenticator_type'],
306 );
307 }
308 wp_send_json_success( $response );
309 } catch ( Error $error ) {
310 $this->delete_trans( 'pub_key_cco', $client_id );
311 wp_send_json_error( $error->getMessage() );
312 } catch ( Exception $exception ) {
313 $this->delete_trans( 'pub_key_cco', $client_id );
314 wp_send_json_error( $exception->getMessage() );
315 }
316 }
317
318 /**
319 * Remove authenticator.
320 *
321 * @return void
322 * @throws Exception If something goes wrong.
323 */
324 public function remove_authenticator(): void {
325 try {
326 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
327 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
328 }
329
330 $cred_id = defender_get_data_from_request( 'key', 'p' );
331 if ( empty( $cred_id ) ) {
332 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
333 }
334
335 $cred_id = $this->base64url_decode( $cred_id );
336 $user_id = get_current_user_id();
337 $option_user_credentials = $this->service->getCredentials( $user_id );
338
339 if ( isset( $option_user_credentials[ $cred_id ] ) ) {
340 unset( $option_user_credentials[ $cred_id ] );
341 $this->service->setCredentials( $user_id, $option_user_credentials );
342 $this->service->removeUserHandleMatchFailed( $user_id, $cred_id );
343
344 if ( 0 === count( $option_user_credentials ) ) {
345 $enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true );
346 if ( empty( $enabled_providers ) ) {
347 $enabled_providers = array();
348 }
349 $key = array_search( Webauthn_Provider::$slug, $enabled_providers, true );
350 if ( false !== $key ) {
351 unset( $enabled_providers[ $key ] );
352 update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers );
353 }
354 }
355
356 wp_send_json_success( esc_html__( 'Authenticator removed.', 'defender-security' ) );
357 }
358
359 wp_send_json_error( esc_html__( 'Key did not match any registered authenticator.', 'defender-security' ) );
360 } catch ( Error $error ) {
361 wp_send_json_error( $error->getMessage() );
362 } catch ( Exception $exception ) {
363 wp_send_json_error( $exception->getMessage() );
364 }
365 }
366
367 /**
368 * Rename authenticator.
369 *
370 * @return void
371 * @throws Exception If something goes wrong.
372 */
373 public function rename_authenticator(): void {
374 try {
375 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
376 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
377 }
378
379 $posted_data = defender_get_data_from_request( null, 'p' );
380 if ( empty( $posted_data['key'] ) || empty( $posted_data['label'] ) ) {
381 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
382 }
383
384 $cred_id = sanitize_text_field( $posted_data['key'] );
385 $cred_id = $this->base64url_decode( $cred_id );
386 $new_label = sanitize_text_field( $posted_data['label'] );
387 $user_id = get_current_user_id();
388 $option_user_credentials = $this->service->getCredentials( $user_id );
389
390 if ( isset( $option_user_credentials[ $cred_id ] ) ) {
391 $option_user_credentials[ $cred_id ]['label'] = $new_label;
392 $this->service->setCredentials( $user_id, $option_user_credentials );
393
394 wp_send_json_success( esc_html__( 'Authenticator identifier renamed.', 'defender-security' ) );
395 }
396
397 wp_send_json_error( esc_html__( 'Key did not match any registered authenticator.', 'defender-security' ) );
398 } catch ( Error $error ) {
399 wp_send_json_error( $error->getMessage() );
400 } catch ( Exception $exception ) {
401 wp_send_json_error( $exception->getMessage() );
402 }
403 }
404
405 /**
406 * Get credential request option.
407 *
408 * @return void
409 * @throws Exception If something goes wrong.
410 */
411 public function get_credential_request_option(): void {
412 try {
413 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
414 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
415 }
416
417 $posted_data = defender_get_data_from_request( null, 'p' );
418 if ( empty( $posted_data['username'] ) ) {
419 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
420 }
421
422 $rp_entity = new PublicKeyCredentialRpEntity(
423 $this->get_site_name(),
424 $this->get_site_domain()
425 );
426
427 $server = new Server(
428 $rp_entity,
429 $this->service,
430 null
431 );
432
433 $user_entity = false;
434 $username = sanitize_text_field( $posted_data['username'] );
435 $user = get_user_by( 'login', $username );
436 if ( is_object( $user ) ) {
437 $user_entity = $this->get_user_entity( $user->ID );
438 }
439
440 if ( false === $user_entity ) {
441 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
442 }
443
444 $auth_type = ! empty( $posted_data['type'] ) ? sanitize_text_field( $posted_data['type'] ) : null;
445 if ( in_array( $auth_type, self::ALLOWED_AUTH_TYPES, true ) ) {
446 $credential_sources = $this->service->findAllForUserByType( $user->ID, $auth_type );
447 } else {
448 $credential_sources = $this->service->findAllForUserEntity( $user_entity );
449 }
450
451 if ( ! is_array( $credential_sources ) || 0 === count( $credential_sources ) ) {
452 throw new Exception( esc_html__( 'Please register a device first to authenticate it.', 'defender-security' ), 100 );
453 }
454
455 // Convert the Credential Sources into Public Key Credential Descriptors for excluding.
456 $allowed_credentials = array_map(
457 function ( $credential ) {
458 return $credential->getPublicKeyCredentialDescriptor();
459 },
460 $credential_sources
461 );
462 $user_verification = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
463
464 $public_key_credential_request_options = $server->generatePublicKeyCredentialRequestOptions(
465 $user_verification,
466 $allowed_credentials
467 );
468
469 // set transition for later use.
470 $client_id = time() . defender_generate_random_string( 24 );
471 $this->set_trans_val( 'pub_key_cro', base64_encode( wp_json_encode( $public_key_credential_request_options ) ), $client_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
472
473 $public_key_credential_request_options = json_decode(
474 wp_json_encode( $public_key_credential_request_options ),
475 true
476 );
477 $public_key_credential_request_options['clientID'] = $client_id;
478 wp_send_json_success( $public_key_credential_request_options );
479 } catch ( Error $error ) {
480 wp_send_json_error( $error->getMessage() );
481 } catch ( Exception $exception ) {
482 wp_send_json_error(
483 array(
484 'message' => $exception->getMessage(),
485 'code' => $exception->getCode(),
486 )
487 );
488 }
489 }
490
491 /**
492 * Verify response.
493 *
494 * @param bool $will_return Either return array or echo json.
495 *
496 * @return array
497 * @throws Exception If something goes wrong.
498 */
499 public function verify_response( bool $will_return = false ) {
500 $client_id = null;
501 try {
502 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
503 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
504 }
505
506 $posted_data = defender_get_data_from_request( null, 'p' );
507 if ( empty( $posted_data['data'] ) || empty( $posted_data['username'] ) || empty( $posted_data['client_id'] ) ) {
508 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
509 }
510
511 $user_entity = false;
512 $response_data = base64_decode( $posted_data['data'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
513 $username = sanitize_text_field( $posted_data['username'] );
514 $client_id = sanitize_text_field( $posted_data['client_id'] );
515 $pub_key_cro = PublicKeyCredentialRequestOptions::createFromString( base64_decode( $this->get_trans_val( 'pub_key_cro', $client_id ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
516 $user = get_user_by( 'login', $username );
517
518 if ( is_object( $user ) ) {
519 $user_entity = $this->get_user_entity( $user->ID );
520 }
521
522 if ( false === $user_entity ) {
523 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
524 }
525
526 $psr17_factory = new Psr17Factory();
527 $creator = new ServerRequestCreator(
528 $psr17_factory,
529 $psr17_factory,
530 $psr17_factory,
531 $psr17_factory
532 );
533
534 $server_request = $creator->fromGlobals();
535
536 $rp_entity = new PublicKeyCredentialRpEntity(
537 $this->get_site_name(),
538 $this->get_site_domain()
539 );
540
541 $server = new Server(
542 $rp_entity,
543 $this->service,
544 null
545 );
546
547 $server->loadAndCheckAssertionResponse(
548 $response_data,
549 $pub_key_cro,
550 $user_entity,
551 $server_request
552 );
553
554 $this->delete_trans( 'pub_key_cro', $client_id );
555
556 $data = esc_html__( 'Authenticator verified successfully.', 'defender-security' );
557
558 return defender_maybe_echo_json( $data, true, $will_return );
559 } catch ( Error $error ) {
560 $this->delete_trans( 'pub_key_cro', $client_id );
561
562 return defender_maybe_echo_json( $error->getMessage(), false, $will_return );
563 } catch ( Exception $exception ) {
564 $data = array( 'message' => $exception->getMessage() );
565
566 if ( $exception->getMessage() === 'Invalid user handle' ) {
567 $user_credentials = $this->service->getCredentials( $user->ID );
568 $decoded_response = json_decode( $response_data, true );
569
570 $data['label'] = $user_credentials[ $decoded_response['rawId'] ]['label'];
571 $data['key'] = $this->base64url_encode( $decoded_response['rawId'] );
572
573 $this->service->addUserHandleMatchFailed( $user, $decoded_response );
574 }
575
576 $this->delete_trans( 'pub_key_cro', $client_id );
577
578 return defender_maybe_echo_json( $data, false, $will_return );
579 }
580 }
581
582 /**
583 * Get translation.
584 *
585 * @return array
586 */
587 public function get_translations(): array {
588 $translations = array(
589 'registration_start' => esc_html__(
590 'Registering a new authenticator is in process.',
591 'defender-security'
592 ),
593 'authenticator_reg_success' => esc_html__(
594 'Registered new authenticator.',
595 'defender-security'
596 ),
597 'authenticator_reg_failed' => esc_html__(
598 'ERROR: Something went wrong.',
599 'defender-security'
600 ),
601 'multiple_reg_attempt' => esc_html__(
602 'Registration failed! The authenticator you are trying to register is already registered with your account.',
603 'defender-security'
604 ),
605 'authentication_start' => esc_html__( 'Authenticating', 'defender-security' ),
606 'authenticator_verification_success' => esc_html__(
607 'Authenticated device successfully.',
608 'defender-security'
609 ),
610 'authenticator_verification_failed' => esc_html__(
611 'Authentication verification failed! Please make sure that biometric functionality is configured on your phone.',
612 'defender-security'
613 ),
614 'authenticator_verification_failed_user_handle_mismatch' => sprintf(
615 /* translators: %s is the name of the authenticator. */
616 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' ),
617 '<strong>%s</strong>'
618 ),
619 'remove_auth' => esc_html__(
620 'Are you sure you want to remove authenticator?',
621 'defender-security'
622 ),
623 'login_failed' => esc_html__(
624 'ERROR: Verification failed.',
625 'defender-security'
626 ),
627 'login_user_handle_match_failed' => esc_html__(
628 '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.',
629 'defender-security'
630 ),
631 'client_webauthn_notice' => esc_html__(
632 'WebAuth is not supported by your web browser. Please install an updated version, or try another browser.',
633 'defender-security'
634 ),
635 'auth_user_handle_mismatch_notice' => esc_html__(
636 'This device might not work. Please delete the device and re-register it.',
637 'defender-security'
638 ),
639 'user_handle_mismatch_main_notice' => esc_html__(
640 '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.',
641 'defender-security'
642 ),
643 );
644
645 if ( ( new WPMUDEV() )->show_support_links() ) {
646 $translations['authenticator_verification_failed'] .= defender_support_ticket_text();
647 }
648
649 return $translations;
650 }
651
652 /**
653 * Disable userHandle mismatch notice.
654 *
655 * @return void
656 * @since 3.4.0
657 */
658 public function disable_user_handle_match_failed_notice(): void {
659 $this->service->disableUserHandleMatchFailedNotice( get_current_user_id() );
660 wp_send_json_success( esc_html__( 'Notice disabled!', 'defender-security' ) );
661 }
662
663 /**
664 * Delete all the data & the cache.
665 */
666 public function remove_data(): void {
667 global $wpdb;
668 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s;", "%{$this->option_prefix}%" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
669 }
670
671 /**
672 * Exports strings.
673 *
674 * @return array An array of strings.
675 */
676 public function export_strings(): array {
677 return array();
678 }
679
680 /**
681 * Converts the current object state to an array.
682 *
683 * @return array The array representation of the object.
684 */
685 public function to_array(): array {
686 return array();
687 }
688
689 /**
690 * Imports data into the model.
691 *
692 * @param array $data Data to be imported into the model.
693 */
694 public function import_data( array $data ) {
695 }
696
697 /**
698 * Removes settings for all submodules.
699 */
700 public function remove_settings(): void {
701 }
702
703 /**
704 * Provides data for the frontend.
705 *
706 * @return array An array of data for the frontend.
707 */
708 public function data_frontend(): array {
709 return array();
710 }
711 }
712