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

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

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