get_user_label();
}
/**
* Get login label.
*
* @return string
*/
public function get_login_label(): string {
return $this->get_user_label();
}
/**
* Get user label.
*
* @return string
*/
public function get_user_label(): string {
return esc_html__( 'Web Authentication', 'defender-security' );
}
/**
* Get the desc of the provider.
*
* @return string
*/
public function get_description(): string {
return esc_html__(
"Authenticate login using your device's built-in fingerprint, facial recognition, or an external hardware security key.",
'defender-security'
);
}
/**
* Display auth method.
*
* @param WP_User $user WP_User object of the logged-in user.
*/
public function user_options( WP_User $user ): void {
$notices = array(
array(
'type' => 'info',
'extra_class' => 'additional-2fa-method',
'style' => 'display: none',
'message' => esc_html__(
'Access from unregistered devices will be denied. To avoid this, consider setting up an additional authentication method.',
'defender-security'
),
),
);
$missed_requirement_message = $this->missed_requirement_message();
$user_has_admin_capability = current_user_can( 'manage_options' );
if ( $user_has_admin_capability ) {
$notice_message = $missed_requirement_message['warning'];
} else {
$notice_message = esc_html__(
'Your site doesn\'t meet the web authentication requirement. Please contact your site\'s administrator to fix this issue.',
'defender-security'
);
}
$all_server_requirement_passed = $missed_requirement_message['all_server_requirement_passed'];
if ( $all_server_requirement_passed ) {
$message = $missed_requirement_message['browser_message'];
$extra_class = ' browser-notice ';
} else {
$message = $notice_message;
$extra_class = ' has-server-error ';
}
$notices[] = array(
'type' => 'warning',
'extra_class' => $extra_class,
'style' => '',
'message' => $message,
);
$translations = wd_di()->get( Webauthn_Controller::class )->get_translations();
$notices[] = array(
'type' => 'warning',
'extra_class' => ' user-handle-mismatch ',
'style' => 'display: none',
'is_dismissible' => true,
'message' => $translations['user_handle_mismatch_main_notice'],
);
$this->get_controller()->render_partial(
'two-fa/providers/biometric',
array(
'notices' => $notices,
)
);
}
/**
* Whether this 2FA provider is configured and available for the user specified.
*
* @param WP_User $user WP_User object of the logged-in user.
*
* @return bool
*/
public function is_available_for_user( WP_User $user ): bool {
return true;
}
/**
* Authentication form markup.
*
* @return void
*/
public function authentication_form() {
?>
get_login_label(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
get( Webauthn_Controller::class );
// Pass $user explicitly to avoid broken binding between authentication stages.
$response = $webauthn_controller->verify_response( true, $user );
if ( isset( $response['success'] ) && true === $response['success'] ) {
return true;
} else {
// No attempt-checks.
$translations = $webauthn_controller->get_translations();
if ( 'Invalid user handle' === $response['data']['message'] ) {
$message = $translations['login_user_handle_match_failed'];
} else {
$message = $translations['login_failed'];
}
return new WP_Error( 'opt_fail', $message );
}
}
/**
* Create missed requirement message.
* Create dynamic message with boolean flag for all server requirement passed.
*
* @return array Dynamic message with boolean flag for all server requirement passed.
*/
private function missed_requirement_message(): array {
$warning_count = 0;
$all_server_requirement_passed = true;
$warning_core_message = '';
if ( ! $this->is_ssl() ) {
++$warning_count;
$all_server_requirement_passed = false;
$warning_core_message .= esc_html__( 'SSL certificate or HTTPS is not forced. ', 'defender-security' );
}
$extension_warning_message = $this->get_extension_warning_message();
if ( '' !== $extension_warning_message ) {
$warning_count += count( $this->get_failed_extension() );
$all_server_requirement_passed = false;
$warning_core_message .= $extension_warning_message;
}
$browser_extension_message = '';
$browser_extension_message .= sprintf(
/* translators: %s: Browser version */
esc_html__(
'The current browser version %s is not compatible with this feature, please update your browser version, or try a different one. ',
'defender-security'
),
'' . $this->get_browser_name() . ''
);
$browser_extension_message .= '';
$warning_prefix = _n(
'Your site doesn\'t meet the web authentication requirement. ',
'Your site doesn\'t meet the web authentication requirements. ',
$warning_count,
'defender-security'
);
$warning_suffix = $this->get_warning_suffix();
$warning = $warning_prefix . $warning_core_message . $browser_extension_message . $warning_suffix;
$browser_message = $warning_prefix . $browser_extension_message . $warning_suffix;
return array(
'warning' => $warning,
'all_server_requirement_passed' => $all_server_requirement_passed,
'browser_message' => $browser_message,
);
}
/**
* Get list of failed extensions.
*
* @return array Array of failed extensions or empty array if all extensions exists.
*/
private function get_failed_extension(): array {
$failed_extensions = array();
if ( ! $this->is_enabled_gmp() ) {
$failed_extensions[] = 'GMP';
}
if ( ! $this->is_enabled_mbstring() ) {
$failed_extensions[] = 'MBSTRING';
}
if ( ! $this->is_enabled_sodium() ) {
$failed_extensions[] = 'SODIUM';
}
return $failed_extensions;
}
/**
* Concatenate multiple failed extensions.
* Form a comma separated with and before last element string from array of elements, if elements is more than two.
*
* @param array $extensions list of failed extensions.
*
* @return string Return concatenated string on success else empty string.
*/
private function concatenate_failed_extensions( array $extensions ): string {
if ( count( $extensions ) === 1 ) {
return $extensions[0];
}
if ( count( $extensions ) > 1 ) {
$last_extension = esc_html__( ' and ', 'defender-security' ) . array_pop( $extensions );
return implode( ', ', $extensions ) . $last_extension;
}
return '';
}
/**
* Create extension warning message
*
* @return string Warning message if extension not met the requirement or empty string for met requirement.
*/
private function get_extension_warning_message(): string {
$message = '';
$failed_extensions = $this->get_failed_extension();
$failed_extensions_count = count( $failed_extensions );
if ( $failed_extensions_count > 0 ) {
$extension_string = $this->concatenate_failed_extensions( $failed_extensions );
$message .= sprintf(
/* translators: %s: Missing PHP extension(s) */
_n(
'The %s extension is not active on your server. ',
'The %s extensions are not active on your server. ',
$failed_extensions_count,
'defender-security'
),
$extension_string
);
}
return $message;
}
/**
* Retrieves the browser name and optionally the version.
*
* @param bool $with_version Whether to include the version number.
*
* @return string Browser name, optionally including the version.
*/
private function get_browser_name( $with_version = true ): string {
if ( ! function_exists( 'wp_check_browser_version' ) ) {
include_once ABSPATH . 'wp-admin/includes/dashboard.php';
}
$browser = wp_check_browser_version();
$browser_string = $browser['name'] . ( $with_version ? ' ' . $browser['version'] : '' );
return $browser_string;
}
/**
* Determine the warning suffix.
*
* @return string Warning suffix sentence.
*/
private function get_warning_suffix(): string {
$warning_suffix = '';
if ( ( new WPMUDEV() )->show_support_links() ) {
$warning_suffix = defender_support_ticket_text();
}
return $warning_suffix;
}
/**
* Check if OTP screen should be shown to user.
*
* @param WP_User $user The user to check.
*
* @return bool
* @since 3.3.0
*/
public function is_otp_screen_available( WP_User $user ): bool {
$is_available = false;
$service_two_fa = wd_di()->get( Two_Fa::class );
$enabled_providers = $service_two_fa->get_enabled_providers_for_user( $user );
if ( isset( $user->ID ) && $user->ID > 0 && true === in_array( self::$slug, $enabled_providers, true ) ) {
$controller = wd_di()->get( Webauthn_Controller::class );
$service_webauthn = wd_di()->get( Webauthn_Component::class );
$user_entity = $controller->get_user_entity( $user->ID );
if ( false !== $user_entity ) {
$credential_sources = $service_webauthn->findAllForUserEntity( $user_entity );
if ( is_array( $credential_sources ) && 0 < count( $credential_sources ) ) {
$is_available = true;
}
}
}
return $is_available;
}
}