PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.1.0
Defender Security – Malware Scanner, Login Security & Firewall v3.1.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 / component / two-factor / providers / webauthn.php

webauthn.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at src/component/two-factor/providers/webauthn.php

386 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 declare( strict_types = 1 );
3
4 namespace WP_Defender\Component\Two_Factor\Providers;
5
6 use WP_Defender\Component\Two_Factor\Two_Factor_Provider;
7 use WP_Defender\Traits\Webauthn as Webauthn_Trait;
8 use WP_Defender\Behavior\WPMUDEV;
9 use WP_User;
10 use WP_Error;
11
12 /**
13 * Class Webauthn.
14 * @since 3.0.0
15 * @package WP_Defender\Component\Two_Factor\Providers
16 */
17 class Webauthn extends Two_Factor_Provider {
18 use Webauthn_Trait;
19
20 /**
21 * 2fa provider slug.
22 *
23 * @var string
24 */
25 public static $slug = 'webauthn';
26
27 /**
28 * Constructor for the Biometric provider.
29 */
30 public function __construct() {
31 add_action( 'wd_2fa_user_options_' . self::$slug, [ $this, 'user_options' ] );
32 }
33
34 /**
35 * Get the name of the provider.
36 *
37 * @return string
38 */
39 public function get_label(): string {
40 return sprintf(
41 '%s <span class="wpdef-notice-tag beta">%s</span>',
42 $this->get_user_label(),
43 __( 'Beta', 'wpdef' )
44 );
45 }
46
47 /**
48 * Get login label.
49 *
50 * @return string
51 */
52 public function get_login_label(): string {
53 return $this->get_label();
54 }
55
56 /**
57 * Get user label.
58 *
59 * @return string
60 */
61 public function get_user_label(): string {
62 return __( 'Web Authentication', 'wpdef' );
63 }
64
65 /**
66 * Get the desc of the provider.
67 *
68 * @return string
69 */
70 public function get_description(): string {
71 return __( "Authenticate login using your device's built-in fingerprint, facial recognition, or an external hardware security key.", 'wpdef' );
72 }
73
74 /**
75 * Display auth method.
76 *
77 * @param WP_User $user WP_User object of the logged-in user.
78 */
79 public function user_options( WP_User $user ): void {
80 $notices = [
81 [
82 'type' => 'info',
83 'extra_class' => 'additional-2fa-method',
84 'style' => 'display: none',
85 'message' => __( 'Access from unregistered devices will be denied. To avoid this, consider setting up an additional authentication method.', 'wpdef' ),
86 ],
87 ];
88
89 $missed_requirement_message = $this->missed_requirement_message();
90 $user_has_admin_capability = current_user_can( 'manage_options' );
91
92 if ( $user_has_admin_capability ) {
93 $notice_message = $missed_requirement_message['warning'];
94 } else {
95 $notice_message = __( 'Your site doesn\'t meet the web authentication requirement. Please contact your site\'s administrator to fix this issue.', 'wpdef' );
96 }
97
98 $all_server_requirement_passed = $missed_requirement_message['all_server_requirement_passed'];
99
100 if ( $all_server_requirement_passed ) {
101 $message = $missed_requirement_message['browser_message'];
102 $extra_class = ' browser-notice ';
103 } else {
104 $message = $notice_message;
105 $extra_class = ' has-server-error ';
106 }
107
108 $notices[] = [
109 'type' => 'warning',
110 'extra_class' => $extra_class,
111 'style' => '',
112 'message' => $message,
113 ];
114
115 $this->get_controller()->render_partial(
116 'two-fa/providers/biometric',
117 [
118 'notices' => $notices,
119 ]
120 );
121 }
122
123 /**
124 * Whether this 2FA provider is configured and available for the user specified.
125 *
126 * @param WP_User $user WP_User object of the logged-in user.
127 *
128 * @return bool
129 */
130 public function is_available_for_user( WP_User $user ): bool {
131 return true;
132 }
133
134 /**
135 * Authentication form markup.
136 *
137 * @return void
138 */
139 public function authentication_form() {
140 ?>
141 <div class="welcome-screen">
142 <p class="wpdef-2fa-label"><?php esc_html_e( 'Web Authentication', 'wpdef' ); ?></p>
143 <p><?php esc_html_e( 'Select an authentication method to continue.', 'wpdef' ); ?></p>
144 <div class="option-row" data-authType="platform">
145 <span class="icon biometric"></span>
146 <span>
147 <strong class="option-hd"><?php esc_html_e( 'Biometric Authentication', 'wpdef' ); ?></strong>
148 <?php esc_html_e( 'Use fingerprint or facial recognition', 'wpdef' ); ?>
149 </span>
150 </div>
151 <div class="option-row" data-authType="cross-platform">
152 <span class="icon hardware"></span>
153 <span>
154 <strong class="option-hd"><?php esc_html_e( 'Hardware Key Authentication', 'wpdef' ); ?></strong>
155 <?php esc_html_e( 'Use USB security keys to login', 'wpdef' ); ?>
156 </span>
157 </div>
158 </div>
159 <div class="webauthn-content-wrap">
160 <div class="webauthn-platform" style="display:none;">
161 <p class="wpdef-2fa-label"><?php esc_html_e( 'Biometric Authentication', 'wpdef' ); ?></p>
162 <p class="wpdef-2fa-text">
163 <?php esc_html_e( 'Please verify your identity using fingerprint or facial recognition.', 'wpdef' ); ?>
164 </p>
165 </div>
166 <div class="webauthn-cross-platform" style="display:none;">
167 <p class="wpdef-2fa-label"><?php esc_html_e( 'Hardware Key Authentication', 'wpdef' ); ?></p>
168 <p class="wpdef-2fa-text">
169 <?php esc_html_e( 'Insert and tap on your USB security key to login.', 'wpdef' ); ?>
170 </p>
171 </div>
172 <p class="wpdef-2fa-text wpdef-2fa-biometric-process-desc" style="display: none;">
173 <img class="def-loader" src="<?php echo defender_asset_url( '/assets/img/spinner.svg' ); ?>" alt="loading">
174 <?php esc_html_e( 'Verification in process...', 'wpdef' ); ?>
175 </p>
176 <div class="wpdef-2fa-webauthn-control" style="display:none;">
177 <button type="button" class="button" id="wpdef-2fa-webauthn-back-btn"><?php esc_html_e( 'Back', 'wpdef' ); ?></button>
178 <button type="button" class="button button-primary float-r" id="wpdef-2fa-biometric-retry-btn">
179 <span class="dashicons dashicons-update"></span>
180 <?php esc_html_e( 'Retry', 'wpdef' ); ?>
181 </button>
182 </div>
183 <input type="hidden" id="wpdef-2fa-biometric-data" name="data" />
184 <input type="hidden" id="wpdef-2fa-biometric-username" name="username" />
185 <input type="hidden" id="wpdef-2fa-biometric-client-id" name="client_id" />
186 <input type="hidden" id="wpdef-2fa-biometric-nonce" name="_def_nonce" />
187 <button id="wpdef-2fa-biometric-submit-btn" type="submit" style="display:none;"></button>
188 </div>
189 <?php
190 }
191
192 /**
193 * Whether this 2FA provider is configured and available for the user specified.
194 *
195 * @param WP_User $user
196 *
197 * @return bool|WP_Error
198 */
199 public function validate_authentication( WP_User $user ) {
200 $webauthn_controller = wd_di()->get( \WP_Defender\Controller\Webauthn::class );
201 $response = $webauthn_controller->verify_response( true );
202
203 if ( isset( $response['success'] ) && true === $response['success'] ) {
204 return true;
205 } else {
206 $translations = $webauthn_controller->get_translations();
207 return new WP_Error( 'opt_fail', $translations['login_failed'] );
208 }
209 }
210
211 /**
212 * Create missed requirement message.
213 *
214 * Create dynamic message with boolean flag for all server requirement passed.
215 *
216 * @return array Dynamic message with boolean flag for all server requirement passed.
217 */
218 private function missed_requirement_message(): array {
219 $warning_count = 0;
220 $all_server_requirement_passed = true;
221 $warning_core_message = '';
222
223 if ( ! $this->is_ssl() ) {
224 $warning_count++;
225 $all_server_requirement_passed = false;
226 $warning_core_message .= __( 'SSL certificate or HTTPS is not forced. ', 'wpdef' );
227 }
228
229 $extension_warning_message = $this->get_extension_warning_message();
230
231 if ( $extension_warning_message !== '' ) {
232 $warning_count += count( $this->get_failed_extension() );
233 $all_server_requirement_passed = false;
234 $warning_core_message .= $extension_warning_message;
235 }
236
237 $browser_extension_message = sprintf(
238 /* translators: ... */
239 __(
240 '<span class="browser-incompatible-msg">The current browser version <span class="browser-version">%s</span> is not compatible with this feature, please update your browser version, or try a different one. </span>',
241 'wpdef'
242 ),
243 $this->get_browser_name()
244 );
245
246 $warning_prefix = _n(
247 'Your site doesn\'t meet the web authentication requirement. ',
248 'Your site doesn\'t meet the web authentication requirements. ',
249 $warning_count,
250 'wpdef'
251 );
252 $warning_suffix = $this->get_warning_suffix();
253
254 $warning = $warning_prefix . $warning_core_message . $browser_extension_message . $warning_suffix;
255
256 $browser_message = $warning_prefix . $browser_extension_message . $warning_suffix;
257
258 return [
259 'warning' => $warning,
260 'all_server_requirement_passed' => $all_server_requirement_passed,
261 'browser_message' => $browser_message,
262 ];
263 }
264
265 /**
266 * Get list of failed extensions.
267 *
268 * @return array Array of failed extensions or empty array if all extensions exists.
269 */
270 private function get_failed_extension(): array {
271 $failed_extensions = [];
272
273 if ( ! $this->is_enabled_gmp() ) {
274 $failed_extensions[] = 'GMP';
275 }
276
277 if ( ! $this->is_enabled_mbstring() ) {
278 $failed_extensions[] = 'MBSTRING';
279 }
280
281 if ( ! $this->is_enabled_sodium() ) {
282 $failed_extensions[] = 'SODIUM';
283 }
284
285 return $failed_extensions;
286 }
287
288 /**
289 * Concatenate multiple failed extensions.
290 *
291 * Form a comma separated with and before last element string from array of elements, if elements is more than two.
292 *
293 * @param array $extensions list of failed extensions.
294 *
295 * @return string Return concatenated string on success else empty string.
296 */
297 private function concatenate_failed_extensions( array $extensions ): string {
298 if ( count( $extensions ) === 1 ) {
299 return $extensions[0];
300 }
301
302 if ( count( $extensions ) > 1 ) {
303 $last_extension = __( ' and ', 'wpdef' ) . array_pop( $extensions );
304
305 return implode( ', ', $extensions ) . $last_extension;
306 }
307
308 return '';
309 }
310
311 /**
312 * Create extension warning message
313 *
314 * @return string Warning message if extension not met the requirement or empty string for met requirement.
315 */
316 private function get_extension_warning_message(): string {
317 $message = '';
318
319 $failed_extensions = $this->get_failed_extension();
320
321 if ( count( $failed_extensions ) > 0 ) {
322 $extension = __( 'extension', 'wpdef' );
323 $verb = __( 'is', 'wpdef' );
324
325 if ( count( $failed_extensions ) > 1 ) {
326 $extension = __( 'extensions', 'wpdef' );
327 $verb = __( 'are', 'wpdef' );
328 }
329
330 $extension_string = $this->concatenate_failed_extensions( $failed_extensions );
331
332 $message .= sprintf(
333 /* translators: ... */
334 __(
335 'The <span class="extensions">%1$s %2$s</span> %3$s not active on your server. ',
336 'wpdef'
337 ),
338 $extension_string,
339 $extension,
340 $verb
341 );
342 }
343
344 return $message;
345 }
346
347 /**
348 * Get browser name with optional version concatenated.
349 *
350 * @return string Browser name with or without version.
351 */
352 private function get_browser_name( $with_version = true ): string {
353 if ( ! function_exists( 'wp_check_browser_version' ) ) {
354 include_once ABSPATH . 'wp-admin/includes/dashboard.php';
355 }
356
357 $browser = wp_check_browser_version();
358 $browser_string = $browser['name'] . ( $with_version ? ' ' . $browser['version'] : '' );
359
360 return $browser_string;
361 }
362
363 /**
364 * Determine the warning suffix.
365 *
366 * @return string Warning suffix sentence.
367 */
368 private function get_warning_suffix(): string {
369 $warning_suffix = '';
370
371 if ( ( new WPMUDEV() )->show_support_links() ) {
372 $warning_suffix = sprintf(
373 /* translators: ... */
374 __(
375 'Still, having trouble? <a target="_blank" href="%s">Open a support ticket</a>.',
376 'wpdef'
377 ),
378 WP_DEFENDER_SUPPORT_LINK
379 );
380 }
381
382 return $warning_suffix;
383 }
384
385 }
386