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

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