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

712 lines 23.5 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 is not obfuscation. Just encode the resulting string.
223 $this->set_trans_val( 'pub_key_cco', base64_encode( $encoded ), $client_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
224
225 // Send Challenge.
226 $public_key_credential_creation_options = json_decode( $encoded, true );
227 $public_key_credential_creation_options['clientID'] = $client_id;
228 wp_send_json_success( $public_key_credential_creation_options );
229 } catch ( Error $error ) {
230 wp_send_json_error( $error->getMessage() );
231 } catch ( Exception $exception ) {
232 wp_send_json_error( $exception->getMessage() );
233 }
234 }
235
236 /**
237 * Verify challenge.
238 *
239 * @return void
240 * @throws Exception If something goes wrong.
241 */
242 public function verify_challenge(): void {
243 $client_id = null;
244 try {
245 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
246 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
247 }
248
249 $posted_data = defender_get_data_from_request( null, 'p' );
250 if ( empty( $posted_data['data'] ) || empty( $posted_data['client_id'] ) ) {
251 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
252 }
253
254 $psr17_factory = new Psr17Factory();
255 $creator = new ServerRequestCreator(
256 $psr17_factory,
257 $psr17_factory,
258 $psr17_factory,
259 $psr17_factory
260 );
261
262 $server_request = $creator->fromGlobals();
263
264 $rp_entity = new PublicKeyCredentialRpEntity(
265 $this->get_site_name(),
266 $this->get_site_domain()
267 );
268
269 $server = new Server(
270 $rp_entity,
271 $this->service,
272 null
273 );
274
275 $response_data = base64_decode( sanitize_text_field( $posted_data['data'] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
276 $client_id = sanitize_text_field( $posted_data['client_id'] );
277 $pub_key_cco = $this->get_trans_val( 'pub_key_cco', $client_id );
278 $pub_key_cco = PublicKeyCredentialCreationOptions::createFromString( base64_decode( $pub_key_cco ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
279
280 $public_key_credential_source = $server->loadAndCheckAttestationResponse(
281 $response_data,
282 $pub_key_cco,
283 $server_request
284 );
285
286 $this->service->saveCredentialSource( $public_key_credential_source );
287
288 // Delete transient.
289 $this->delete_trans( 'pub_key_cco', $client_id );
290
291 $response = array();
292 $username = $pub_key_cco->getUser()->getName();
293 $user = get_user_by( 'login', $username );
294
295 $cred_data = array();
296 if ( is_object( $user ) ) {
297 $cred_data = $this->service->getCredentials( $user->ID );
298 }
299
300 $cred_id = base64_encode( $public_key_credential_source->getPublicKeyCredentialId() ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
301 if ( isset( $cred_data[ $cred_id ] ) ) {
302 $response = array(
303 'key' => $this->base64url_encode( $cred_id ),
304 'label' => ucfirst( $cred_data[ $cred_id ]['label'] ),
305 'added' => wp_date( 'Y-m-d', $cred_data[ $cred_id ]['added'] ),
306 'auth_type' => $cred_data[ $cred_id ]['authenticator_type'],
307 );
308 }
309 wp_send_json_success( $response );
310 } catch ( Error $error ) {
311 $this->delete_trans( 'pub_key_cco', $client_id );
312 wp_send_json_error( $error->getMessage() );
313 } catch ( Exception $exception ) {
314 $this->delete_trans( 'pub_key_cco', $client_id );
315 wp_send_json_error( $exception->getMessage() );
316 }
317 }
318
319 /**
320 * Remove authenticator.
321 *
322 * @return void
323 * @throws Exception If something goes wrong.
324 */
325 public function remove_authenticator(): void {
326 try {
327 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
328 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
329 }
330
331 $cred_id = defender_get_data_from_request( 'key', 'p' );
332 if ( empty( $cred_id ) ) {
333 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
334 }
335
336 $cred_id = $this->base64url_decode( $cred_id );
337 $user_id = get_current_user_id();
338 $option_user_credentials = $this->service->getCredentials( $user_id );
339
340 if ( isset( $option_user_credentials[ $cred_id ] ) ) {
341 unset( $option_user_credentials[ $cred_id ] );
342 $this->service->setCredentials( $user_id, $option_user_credentials );
343 $this->service->removeUserHandleMatchFailed( $user_id, $cred_id );
344
345 if ( 0 === count( $option_user_credentials ) ) {
346 $enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true );
347 if ( empty( $enabled_providers ) ) {
348 $enabled_providers = array();
349 }
350 $key = array_search( Webauthn_Provider::$slug, $enabled_providers, true );
351 if ( false !== $key ) {
352 unset( $enabled_providers[ $key ] );
353 update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers );
354 }
355 }
356
357 wp_send_json_success( esc_html__( 'Authenticator removed.', 'defender-security' ) );
358 }
359
360 wp_send_json_error( esc_html__( 'Key did not match any registered authenticator.', 'defender-security' ) );
361 } catch ( Error $error ) {
362 wp_send_json_error( $error->getMessage() );
363 } catch ( Exception $exception ) {
364 wp_send_json_error( $exception->getMessage() );
365 }
366 }
367
368 /**
369 * Rename authenticator.
370 *
371 * @return void
372 * @throws Exception If something goes wrong.
373 */
374 public function rename_authenticator(): void {
375 try {
376 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
377 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
378 }
379
380 $posted_data = defender_get_data_from_request( null, 'p' );
381 if ( empty( $posted_data['key'] ) || empty( $posted_data['label'] ) ) {
382 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
383 }
384
385 $cred_id = sanitize_text_field( $posted_data['key'] );
386 $cred_id = $this->base64url_decode( $cred_id );
387 $new_label = sanitize_text_field( $posted_data['label'] );
388 $user_id = get_current_user_id();
389 $option_user_credentials = $this->service->getCredentials( $user_id );
390
391 if ( isset( $option_user_credentials[ $cred_id ] ) ) {
392 $option_user_credentials[ $cred_id ]['label'] = $new_label;
393 $this->service->setCredentials( $user_id, $option_user_credentials );
394
395 wp_send_json_success( esc_html__( 'Authenticator identifier renamed.', 'defender-security' ) );
396 }
397
398 wp_send_json_error( esc_html__( 'Key did not match any registered authenticator.', 'defender-security' ) );
399 } catch ( Error $error ) {
400 wp_send_json_error( $error->getMessage() );
401 } catch ( Exception $exception ) {
402 wp_send_json_error( $exception->getMessage() );
403 }
404 }
405
406 /**
407 * Get credential request option.
408 *
409 * @return void
410 * @throws Exception If something goes wrong.
411 */
412 public function get_credential_request_option(): void {
413 try {
414 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
415 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
416 }
417
418 $posted_data = defender_get_data_from_request( null, 'p' );
419 if ( empty( $posted_data['username'] ) ) {
420 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
421 }
422
423 $rp_entity = new PublicKeyCredentialRpEntity(
424 $this->get_site_name(),
425 $this->get_site_domain()
426 );
427
428 $server = new Server(
429 $rp_entity,
430 $this->service,
431 null
432 );
433
434 $user_entity = false;
435 $username = sanitize_text_field( $posted_data['username'] );
436 $user = get_user_by( 'login', $username );
437 if ( is_object( $user ) ) {
438 $user_entity = $this->get_user_entity( $user->ID );
439 }
440
441 if ( false === $user_entity ) {
442 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
443 }
444
445 $auth_type = ! empty( $posted_data['type'] ) ? sanitize_text_field( $posted_data['type'] ) : null;
446 if ( in_array( $auth_type, self::ALLOWED_AUTH_TYPES, true ) ) {
447 $credential_sources = $this->service->findAllForUserByType( $user->ID, $auth_type );
448 } else {
449 $credential_sources = $this->service->findAllForUserEntity( $user_entity );
450 }
451
452 if ( ! is_array( $credential_sources ) || 0 === count( $credential_sources ) ) {
453 throw new Exception( esc_html__( 'Please register a device first to authenticate it.', 'defender-security' ), 100 );
454 }
455
456 // Convert the Credential Sources into Public Key Credential Descriptors for excluding.
457 $allowed_credentials = array_map(
458 function ( $credential ) {
459 return $credential->getPublicKeyCredentialDescriptor();
460 },
461 $credential_sources
462 );
463 $user_verification = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
464
465 $public_key_credential_request_options = $server->generatePublicKeyCredentialRequestOptions(
466 $user_verification,
467 $allowed_credentials
468 );
469
470 // Set transition for later use.
471 $encoded = wp_json_encode( $public_key_credential_request_options );
472 $client_id = time() . defender_generate_random_string( 24 );
473 // This is not obfuscation. Just encode the resulting string.
474 $this->set_trans_val( 'pub_key_cro', base64_encode( $encoded ), $client_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
475
476 $public_key_credential_request_options = json_decode( $encoded, true );
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