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

716 lines 23.8 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 // @phpstan-ignore-next-line
70 add_action( 'wp_ajax_defender_webauthn_verify_response', array( $this, 'verify_response' ) );
71 // @phpstan-ignore-next-line
72 add_action( 'wp_ajax_nopriv_defender_webauthn_verify_response', array( $this, 'verify_response' ) );
73 // Handling requests in the frontend.
74 if ( wd_di()->get( Two_Fa_Controller::class )->woo_integration_enabled() ) {
75 add_action( 'wp_ajax_nopriv_defender_webauthn_create_challenge', array( $this, 'create_challenge' ) );
76 add_action( 'wp_ajax_nopriv_defender_webauthn_verify_challenge', array( $this, 'verify_challenge' ) );
77 add_action(
78 'wp_ajax_nopriv_defender_webauthn_remove_authenticator',
79 array(
80 $this,
81 'remove_authenticator',
82 )
83 );
84 add_action(
85 'wp_ajax_nopriv_defender_webauthn_rename_authenticator',
86 array(
87 $this,
88 'rename_authenticator',
89 )
90 );
91 }
92
93 // Disable userHandle mismatch notice.
94 add_action(
95 'wp_ajax_defender_webauthn_disable_user_handle_match_failed_notice',
96 array( $this, 'disable_user_handle_match_failed_notice' )
97 );
98 }
99 }
100
101 /**
102 * Get authenticator records for current user.
103 *
104 * @return array
105 */
106 public function get_current_user_authenticators(): array {
107 $arr = array();
108 $user_id = get_current_user_id();
109 $user_credentials = $this->service->getCredentials( $user_id );
110 if ( array() !== $user_credentials ) {
111 foreach ( $user_credentials as $key => $value ) {
112 $arr[] = array(
113 'key' => $this->base64url_encode( $key ),
114 'label' => $value['label'],
115 'added' => wp_date( 'Y-m-d', $value['added'] ),
116 'auth_type' => $value['authenticator_type'],
117 );
118 }
119 }
120
121 return $arr;
122 }
123
124 /**
125 * Get user entity.
126 *
127 * @param int $user_id User ID.
128 *
129 * @return false|PublicKeyCredentialUserEntity
130 */
131 public function get_user_entity( int $user_id ) {
132 if ( $user_id <= 0 ) {
133 return false;
134 }
135
136 $user = get_user_by( 'id', $user_id );
137 if ( ! is_object( $user ) ) {
138 return false;
139 }
140
141 $user_hash = $this->get_user_hash( $user->user_login );
142 $user_avatar = get_avatar_url( $user->user_email, array( 'scheme' => 'https' ) );
143
144 return new PublicKeyCredentialUserEntity(
145 $user->user_login,
146 $user_hash,
147 $user->display_name,
148 $user_avatar
149 );
150 }
151
152 /**
153 * Create challenge.
154 *
155 * @return void
156 * @throws Exception If something goes wrong.
157 */
158 public function create_challenge(): void {
159 try {
160 if ( ! $this->verify_nonce( 'wpdef_webauthn' ) ) {
161 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
162 }
163
164 $type = defender_get_data_from_request( 'type', 'g' );
165 if ( '' === $type ) {
166 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
167 }
168
169 $user_id = get_current_user_id();
170 $user_entity = $this->get_user_entity( $user_id );
171 if ( false === $user_entity ) {
172 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
173 }
174
175 // Get the list of all authenticators associated to a user.
176 $credential_sources = $this->service->findAllForUserEntity( $user_entity );
177
178 // Convert the Credential Sources into Public Key Credential Descriptors.
179 $exclude_credentials = array_map(
180 function ( $credential ) {
181 return $credential->getPublicKeyCredentialDescriptor();
182 },
183 $credential_sources
184 );
185
186 if ( 'platform' === $type ) {
187 $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM;
188 } elseif ( 'cross-platform' === $type ) {
189 $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
190 } else {
191 $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE;
192 }
193
194 // Create authenticator selection.
195 $authenticator_selection_criteria = new AuthenticatorSelectionCriteria();
196 $authenticator_selection_criteria->setAuthenticatorAttachment( $authenticator_type );
197 $authenticator_selection_criteria->setRequireResidentKey( false );
198 $authenticator_selection_criteria->setUserVerification( AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED );
199
200 $rp_entity = new PublicKeyCredentialRpEntity(
201 $this->get_site_name(),
202 $this->get_site_domain()
203 );
204 $server = new Server(
205 $rp_entity,
206 $this->service,
207 null
208 );
209
210 $public_key_credential_creation_options = $server->generatePublicKeyCredentialCreationOptions(
211 $user_entity,
212 PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
213 $exclude_credentials,
214 $authenticator_selection_criteria
215 );
216
217 $client_id = time() . defender_generate_random_string( 24 );
218
219 // Set transition for later use.
220 $encoded = wp_json_encode( $public_key_credential_creation_options );
221 // This is not obfuscation. Just encode the resulting string.
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 ( '' === $posted_data['data'] || '' === $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'] ), true ); // 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, true ) ); // 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 ( '' === $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 ( ! is_array( $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 ( ! isset( $posted_data['key'], $posted_data['label'] ) || '' === $posted_data['key'] || '' === $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 ( ! isset( $posted_data['username'] ) || '' === $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 = isset( $posted_data['type'] ) && '' !== $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 $encoded = wp_json_encode( $public_key_credential_request_options );
471 $client_id = time() . defender_generate_random_string( 24 );
472 // This is not obfuscation. Just encode the resulting string.
473 $this->set_trans_val( 'pub_key_cro', base64_encode( $encoded ), $client_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
474
475 $public_key_credential_request_options = json_decode( $encoded, true );
476 $public_key_credential_request_options['clientID'] = $client_id;
477 wp_send_json_success( $public_key_credential_request_options );
478 } catch ( Error $error ) {
479 wp_send_json_error( $error->getMessage() );
480 } catch ( Exception $exception ) {
481 wp_send_json_error(
482 array(
483 'message' => $exception->getMessage(),
484 'code' => $exception->getCode(),
485 )
486 );
487 }
488 }
489
490 /**
491 * Verify response.
492 *
493 * @param bool $will_return Either return array or echo json.
494 *
495 * @return array|void
496 * @throws Exception If something goes wrong.
497 */
498 public function verify_response( bool $will_return = false ) {
499 $client_id = null;
500 try {
501 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
502 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
503 }
504
505 $posted_data = defender_get_data_from_request( null, 'p' );
506 if (
507 ! isset( $posted_data['data'], $posted_data['username'], $posted_data['client_id'] )
508 || '' === $posted_data['data']
509 || '' === $posted_data['username']
510 || '' === $posted_data['client_id']
511 ) {
512 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
513 }
514
515 $user_entity = false;
516 $response_data = base64_decode( $posted_data['data'], true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
517 $username = sanitize_text_field( $posted_data['username'] );
518 $client_id = sanitize_text_field( $posted_data['client_id'] );
519 $pub_key_cro = PublicKeyCredentialRequestOptions::createFromString( base64_decode( $this->get_trans_val( 'pub_key_cro', $client_id ), true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
520 $user = get_user_by( 'login', $username );
521
522 if ( is_object( $user ) ) {
523 $user_entity = $this->get_user_entity( $user->ID );
524 }
525
526 if ( false === $user_entity ) {
527 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
528 }
529
530 $psr17_factory = new Psr17Factory();
531 $creator = new ServerRequestCreator(
532 $psr17_factory,
533 $psr17_factory,
534 $psr17_factory,
535 $psr17_factory
536 );
537
538 $server_request = $creator->fromGlobals();
539
540 $rp_entity = new PublicKeyCredentialRpEntity(
541 $this->get_site_name(),
542 $this->get_site_domain()
543 );
544
545 $server = new Server(
546 $rp_entity,
547 $this->service,
548 null
549 );
550
551 $server->loadAndCheckAssertionResponse(
552 $response_data,
553 $pub_key_cro,
554 $user_entity,
555 $server_request
556 );
557
558 $this->delete_trans( 'pub_key_cro', $client_id );
559
560 $data = esc_html__( 'Authenticator verified successfully.', 'defender-security' );
561
562 return defender_maybe_echo_json( $data, true, $will_return );
563 } catch ( Error $error ) {
564 $this->delete_trans( 'pub_key_cro', $client_id );
565
566 return defender_maybe_echo_json( $error->getMessage(), false, $will_return );
567 } catch ( Exception $exception ) {
568 $data = array( 'message' => $exception->getMessage() );
569
570 if ( $exception->getMessage() === 'Invalid user handle' ) {
571 $user_credentials = $this->service->getCredentials( $user->ID );
572 $decoded_response = json_decode( $response_data, true );
573
574 $data['label'] = $user_credentials[ $decoded_response['rawId'] ]['label'];
575 $data['key'] = $this->base64url_encode( $decoded_response['rawId'] );
576
577 $this->service->addUserHandleMatchFailed( $user, $decoded_response );
578 }
579
580 $this->delete_trans( 'pub_key_cro', $client_id );
581
582 return defender_maybe_echo_json( $data, false, $will_return );
583 }
584 }
585
586 /**
587 * Get translation.
588 *
589 * @return array
590 */
591 public function get_translations(): array {
592 $translations = array(
593 'registration_start' => esc_html__(
594 'Registering a new authenticator is in process.',
595 'defender-security'
596 ),
597 'authenticator_reg_success' => esc_html__(
598 'Registered new authenticator.',
599 'defender-security'
600 ),
601 'authenticator_reg_failed' => esc_html__(
602 'ERROR: Something went wrong.',
603 'defender-security'
604 ),
605 'multiple_reg_attempt' => esc_html__(
606 'Registration failed! The authenticator you are trying to register is already registered with your account.',
607 'defender-security'
608 ),
609 'authentication_start' => esc_html__( 'Authenticating', 'defender-security' ),
610 'authenticator_verification_success' => esc_html__(
611 'Authenticated device successfully.',
612 'defender-security'
613 ),
614 'authenticator_verification_failed' => esc_html__(
615 'Authentication verification failed! Please make sure that biometric functionality is configured on your phone.',
616 'defender-security'
617 ),
618 'authenticator_verification_failed_user_handle_mismatch' => sprintf(
619 /* translators: %s is the name of the authenticator. */
620 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' ),
621 '<strong>%s</strong>'
622 ),
623 'remove_auth' => esc_html__(
624 'Are you sure you want to remove authenticator?',
625 'defender-security'
626 ),
627 'login_failed' => esc_html__(
628 'ERROR: Verification failed.',
629 'defender-security'
630 ),
631 'login_user_handle_match_failed' => esc_html__(
632 '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.',
633 'defender-security'
634 ),
635 'client_webauthn_notice' => esc_html__(
636 'WebAuth is not supported by your web browser. Please install an updated version, or try another browser.',
637 'defender-security'
638 ),
639 'auth_user_handle_mismatch_notice' => esc_html__(
640 'This device might not work. Please delete the device and re-register it.',
641 'defender-security'
642 ),
643 'user_handle_mismatch_main_notice' => esc_html__(
644 '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.',
645 'defender-security'
646 ),
647 );
648
649 if ( ( new WPMUDEV() )->show_support_links() ) {
650 $translations['authenticator_verification_failed'] .= defender_support_ticket_text();
651 }
652
653 return $translations;
654 }
655
656 /**
657 * Disable userHandle mismatch notice.
658 *
659 * @return void
660 * @since 3.4.0
661 */
662 public function disable_user_handle_match_failed_notice(): void {
663 $this->service->disableUserHandleMatchFailedNotice( get_current_user_id() );
664 wp_send_json_success( esc_html__( 'Notice disabled!', 'defender-security' ) );
665 }
666
667 /**
668 * Delete all the data & the cache.
669 */
670 public function remove_data(): void {
671 global $wpdb;
672 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s;", "%{$this->option_prefix}%" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
673 }
674
675 /**
676 * Exports strings.
677 *
678 * @return array An array of strings.
679 */
680 public function export_strings(): array {
681 return array();
682 }
683
684 /**
685 * Converts the current object state to an array.
686 *
687 * @return array The array representation of the object.
688 */
689 public function to_array(): array {
690 return array();
691 }
692
693 /**
694 * Imports data into the model.
695 *
696 * @param array $data Data to be imported into the model.
697 */
698 public function import_data( array $data ) {
699 }
700
701 /**
702 * Removes settings for all submodules.
703 */
704 public function remove_settings(): void {
705 }
706
707 /**
708 * Provides data for the frontend.
709 *
710 * @return array An array of data for the frontend.
711 */
712 public function data_frontend(): array {
713 return array();
714 }
715 }
716