PluginProbe
Patchstack – WordPress & Plugins Security / 2.1.3
Patchstack – WordPress & Plugins Security v2.1.3
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
← All changes | includes/login.php +323 -436 trunk2.1.3 View file →
@@ -1,436 +1,323 @@
1 -<?php
2 -
3 -// Do not allow the file to be called directly.
4 -if ( ! defined( 'ABSPATH' ) ) {
5 - exit;
6 -}
7 -
8 -/**
9 - * This class is used to alter anything related to the login page.
10 - */
11 -class P_Login extends P_Core {
12 -
13 - /**
14 - * Validated request or not.
15 - *
16 - * @param boolean
17 - */
18 - private $validated = false;
19 -
20 - /**
21 - * Add the actions required to interact with the login process.
22 - *
23 - * @param Patchstack $core
24 - * @return void
25 - */
26 - public function __construct( $core ) {
27 - parent::__construct( $core );
28 -
29 - if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
30 - return;
31 - }
32 -
33 - add_action( 'login_init', [ $this, 'add_captcha' ] );
34 - add_action( 'login_init', [ $this, 'check_ipban' ] );
35 - add_action( 'login_head', [ $this, 'add_captcha' ] );
36 - add_action( 'login_enqueue_scripts', [ $this, 'login_enqueue_scripts' ], 1 );
37 -
38 - // WooCommerce related functionality.
39 - if ( class_exists( 'WooCommerce' ) ) {
40 - add_action( 'woocommerce_login_form_start', [ $this, 'add_captcha' ] );
41 - add_action( 'woocommerce_register_form_start', [ $this, 'add_captcha' ] );
42 - add_action( 'wp_authenticate', [ $this, 'add_captcha' ] );
43 - add_action( 'woocommerce_before_lost_password_form', [ $this, 'add_captcha' ] );
44 - }
45 -
46 - // 2FA related actions.
47 - if ( $this->get_option( 'patchstack_login_2fa', 0 ) ) {
48 - add_action( 'login_form', [ $this, 'tfa_login_form' ] );
49 - add_action( 'authenticate', [ $this, 'tfa_authenticate' ], 30, 3 );
50 - add_action( 'profile_personal_options', [ $this, 'tfa_profile_personal_options' ] );
51 - add_action( 'personal_options', [ $this, 'tfa_personal_options' ] );
52 - add_action( 'edit_user_profile_update', [ $this, 'tfa_options_update' ] );
53 - add_action( 'personal_options_update', [ $this, 'tfa_personal_options_update' ] );
54 - add_action( 'admin_enqueue_scripts', [ $this, 'tfa_admin_enqueue_scripts' ] );
55 -
56 - // WooCommerce related functionality.
57 - if ( class_exists( 'WooCommerce' ) ) {
58 - add_action( 'woocommerce_login_form', [ $this, 'tfa_woocommerce_login_form' ] );
59 - add_action( 'woocommerce_edit_account_form', [ $this, 'tfa_woocommerce_profile_personal_options' ] );
60 - add_action( 'woocommerce_save_account_details_errors', [ $this, 'tfa_woocommerce_validate_tfa'], 10, 2 );
61 - }
62 - }
63 - }
64 -
65 - /**
66 - * Register the Google reCAPTCHA JavaScript for the login area.
67 - *
68 - * @return void
69 - */
70 - public function login_enqueue_scripts() {
71 - if ( $this->get_option( 'patchstack_captcha_login_form', false ) && $this->get_option( 'patchstack_captcha_type' ) != 'v3' && $this->get_option( 'patchstack_captcha_type' ) != 'turnstile' ) {
72 - wp_enqueue_script( 'patchstack_captcha', 'https://www.google.com/recaptcha/api.js' );
73 - }
74 - }
75 -
76 - /**
77 - * Add the 2FA code to the login form.
78 - *
79 - * @return void
80 - */
81 - public function tfa_login_form() {
82 - require_once dirname( __FILE__ ) . '/views/2fa-login-form.php';
83 - }
84 -
85 - /**
86 - * Add the 2FA code to the WooCommerce login form.
87 - *
88 - * @return void
89 - */
90 - public function tfa_woocommerce_login_form() {
91 - require_once dirname( __FILE__ ) . '/views/2fa-login-form-woocommerce.php';
92 - }
93 -
94 - /**
95 - * Check the 2FA code, if 2FA is enabled for the user.
96 - *
97 - * @param object $user
98 - * @param string $username
99 - * @param string $password
100 - * @return object|WP_User|WP_Error
101 - */
102 - public function tfa_authenticate( $user, $username = '', $password = '' ) {
103 - if ( ! isset( $user->ID ) ) {
104 - return $user;
105 - }
106 -
107 - // If we have a valid user object, check to see if the user has 2FA enabled.
108 - $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
109 - if ( empty( $enabled ) ) {
110 - return $user;
111 - }
112 -
113 - // If enabled, check to see if the verification code is being sent.
114 - if ( ! isset( $_POST['patchstack_2fa'] ) || ( isset( $_POST['patchstack_2fa'] ) && $_POST['patchstack_2fa'] == '' ) ) {
115 - return new WP_Error( 'patchstack_2fa_empty_code', esc_attr__( 'Please enter the 2FA authentication code that is generated on your device.', 'patchstack' ) );
116 - }
117 -
118 - // Verify the code.
119 - require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
120 - $secret = $this->tfa_get_secret( $user );
121 - if ( ! TokenAuth6238::verify( $secret, trim( $_POST['patchstack_2fa'] ) ) ) {
122 - return new WP_Error( 'patchstack_2fa_invalid_code', esc_attr__( 'The 2FA authentication code you entered is invalid.', 'patchstack' ) );
123 - }
124 -
125 - return $user;
126 - }
127 -
128 - /**
129 - * Show the 2FA disable field to the admin.
130 - *
131 - * @param object $user
132 - * @return void
133 - */
134 - public function tfa_personal_options( $user ) {
135 - require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration-admin.php';
136 - }
137 -
138 - /**
139 - * Show the 2FA fields.
140 - *
141 - * @param object $user
142 - * @return void
143 - */
144 - public function tfa_profile_personal_options( $user ) {
145 - $secret = $this->tfa_get_secret( $user );
146 - require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration.php';
147 - }
148 -
149 - /**
150 - * Show the 2FA fields.
151 - *
152 - * @param object $user
153 - * @return void
154 - */
155 - public function tfa_woocommerce_profile_personal_options( $user ) {
156 - $secret = $this->tfa_get_secret( $user );
157 - require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration-woocommerce.php';
158 - }
159 -
160 - /**
161 - * Validate the 2FA connection of a WooCommerce customer.
162 - *
163 - * @param mixed $errors
164 - * @param mixed $user
165 - * @return void
166 - */
167 - public function tfa_woocommerce_validate_tfa( &$errors, &$user ) {
168 - // If we have a valid user object, check to see if the user has 2FA enabled.
169 - $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
170 - if ( $enabled || ! isset ( $_POST['patchstack_2fa_enabled'] ) ) {
171 - $this->tfa_personal_options_update( $user->ID );
172 - return;
173 - }
174 -
175 - // Verify the code.
176 - require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
177 - $secret = $this->tfa_get_secret( $user );
178 - $code = isset( $_POST['patchstack_2fa_secretkey_verification'] ) ? trim( $_POST['patchstack_2fa_secretkey_verification'] ) : '';
179 - if ( ! TokenAuth6238::verify( $secret, $code ) ) {
180 - wc_add_notice( __( 'The 2FA authentication code you entered is invalid.', 'patchstack' ), 'error' );
181 - return;
182 - }
183 -
184 - $this->tfa_personal_options_update( $user->ID );
185 - }
186 -
187 - /**
188 - * Update the 2FA fields.
189 - *
190 - * @param integer $user_id
191 - * @return void
192 - */
193 - public function tfa_personal_options_update( $user_id ) {
194 - update_user_option( $user_id, 'webarx_2fa_enabled', ! empty( $_POST['patchstack_2fa_enabled'] ), true );
195 - }
196 -
197 - /**
198 - * Update the 2FA fields on admin.
199 - *
200 - * @param integer $user_id
201 - * @return void
202 - */
203 - public function tfa_options_update( $user_id ) {
204 - update_user_option( $user_id, 'webarx_2fa_enabled', ! empty( $_POST['patchstack_2fa_enabled'] ), true );
205 - }
206 -
207 - /**
208 - * Add the QRCode image generator JavaScript library.
209 - *
210 - * @return void
211 - */
212 - public function tfa_admin_enqueue_scripts() {
213 - wp_register_script( 'patchstack_qrcode', $this->plugin->url . '/assets/js/qrcode.min.js', [], $this->plugin->version );
214 - wp_enqueue_script( 'patchstack_qrcode' );
215 - }
216 -
217 - /**
218 - * In case of legacy conditions, we encrypt the secret key and then store it.
219 - *
220 - * @return string
221 - */
222 - private function tfa_get_secret( $user ) {
223 - $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
224 -
225 - // If user has no secret key set yet, generate one.
226 - if ( empty( $secret ) || strlen( $secret ) === 16 ) {
227 - if ( empty( $secret ) ) {
228 - require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
229 - $secret = TokenAuth6238::generateRandomClue();
230 - }
231 -
232 - $enc = $this->encrypt( $secret );
233 - update_user_option( $user->ID, 'webarx_2fa_secretkey', $enc['cipher'], true );
234 - update_user_option( $user->ID, 'webarx_2fa_secretkey_nonce', $enc['nonce'], true );
235 - } else {
236 - $nonce = get_user_option( 'webarx_2fa_secretkey_nonce', $user->ID );
237 - $secret = $this->decrypt( $secret, $nonce );
238 - }
239 -
240 - return $secret;
241 - }
242 -
243 - /**
244 - * Check if the IP address is banned from attempting to guess passwords.
245 - *
246 - * @return void
247 - */
248 - public function check_ipban() {
249 - if ( is_user_logged_in() || ! $this->get_option( 'patchstack_block_bruteforce_ips', 0 ) ) {
250 - return;
251 - }
252 -
253 - // Check if the users IP address is whitelisted.
254 - $ip = $this->get_ip();
255 - if ( $this->plugin->ban->is_ip_whitelisted( $ip ) ) {
256 - return;
257 - }
258 -
259 - // Calculate block time.
260 - $minutes = (int) $this->get_option( 'patchstack_anti_bruteforce_minutes', 30 );
261 - $timeout = (int) $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 );
262 - if ( empty( $minutes ) || empty( $timeout ) ) {
263 - $time = 30 + 60;
264 - } else {
265 - $time = $minutes + $timeout;
266 - }
267 -
268 - // Check if X failed login attempts were made.
269 - global $wpdb;
270 - $results = $wpdb->get_results(
271 - $wpdb->prepare( 'SELECT COUNT(*) AS numIps FROM ' . $wpdb->prefix . "patchstack_event_log WHERE ip = '%s' AND action = 'failed login' AND date >= ('" . current_time( 'mysql' ) . "' - INTERVAL %d MINUTE)", [ $ip, $time ] ),
272 - OBJECT
273 - );
274 -
275 - // Determine the number of attempts.
276 - if ( ! isset( $results, $results[0], $results[0]->numIps ) ) {
277 - $num = 0;
278 - } else {
279 - $num = $results[0]->numIps;
280 - }
281 -
282 - // Block the user?
283 - if ( $num >= $this->get_option( 'patchstack_anti_bruteforce_attempts', 10 ) ) {
284 - $this->plugin->firewall_base->display_error_page( 24 );
285 - }
286 - }
287 -
288 - /**
289 - * Determine if we should inject reCAPTCHA into certain pages.
290 - *
291 - * @return void
292 - */
293 - public function add_captcha() {
294 - switch ( $this->get_option( 'patchstack_captcha_type' ) ) {
295 - case 'v2':
296 - $public = $this->get_option( 'patchstack_captcha_public_key', '' );
297 - $private = $this->get_option( 'patchstack_captcha_private_key', '' );
298 - break;
299 - case 'invisible':
300 - $public = $this->get_option( 'patchstack_captcha_public_key_v3', '' );
301 - $private = $this->get_option( 'patchstack_captcha_private_key_v3', '' );
302 - break;
303 - case 'v3':
304 - $public = $this->get_option( 'patchstack_captcha_public_key_v3_new', '' );
305 - $private = $this->get_option( 'patchstack_captcha_private_key_v3_new', '' );
306 - break;
307 - case 'turnstile':
308 - $public = $this->get_option( 'patchstack_captcha_public_key_turnstile', '' );
309 - $private = $this->get_option( 'patchstack_captcha_private_key_turnstile', '' );
310 - break;
311 - default:
312 - return;
313 - break;
314 - }
315 -
316 - // Make sure that the keys are set.
317 - if ( $public == '' || $private == '' ) {
318 - return;
319 - }
320 -
321 - // reCAPTCHA on the login page.
322 - if ( $this->get_option( 'patchstack_captcha_login_form' ) ) {
323 - add_filter( 'login_form', [ $this->plugin->hardening, 'captcha_display' ] );
324 - add_filter( 'woocommerce_login_form', [ $this->plugin->hardening, 'captcha_display' ] );
325 - add_filter( 'wp_authenticate_user', [ $this, 'login_captcha_check' ], 10, 2 );
326 - }
327 -
328 - // reCAPTCHA on the registration form.
329 - if ( $this->get_option( 'patchstack_captcha_registration_form' ) ) {
330 - add_action( 'register_form', [ $this->plugin->hardening, 'captcha_display' ] );
331 - add_action( 'woocommerce_register_form', [ $this->plugin->hardening, 'captcha_display' ] );
332 - add_action( 'registration_errors', [ $this, 'general_captcha_check' ] );
333 - add_filter( 'woocommerce_process_registration_errors', [$this, 'general_captcha_check' ], 10, 1 );
334 - }
335 -
336 - // reCAPTCHA on the reset password form.
337 - if ( $this->get_option( 'patchstack_captcha_reset_pwd_form' ) ) {
338 - add_action( 'lostpassword_form', [ $this->plugin->hardening, 'captcha_display' ] );
339 - add_action( 'woocommerce_lostpassword_form', [ $this->plugin->hardening, 'captcha_display' ] );
340 - add_action( 'allow_password_reset', [ $this, 'general_captcha_check' ] );
341 -
342 - // WooCommerce only.
343 - if ( class_exists( 'WooCommerce' ) ) {
344 - add_action( 'lostpassword_post', [ $this, 'general_captcha_check' ], 1, 1 );
345 - }
346 - }
347 - }
348 -
349 - /**
350 - * Check reCAPTCHA upon login.
351 - *
352 - * @param string $user
353 - * @param string $password
354 - * @return WP_User|WP_Error
355 - */
356 - public function login_captcha_check( $user, $password ) {
357 - if ( $this->validated ) {
358 - return $user;
359 - }
360 -
361 - $result = $this->plugin->hardening->captcha_check();
362 -
363 - if ( ! $result['response'] ) {
364 - if ( $result['reason'] === 'ERROR_NO_KEYS' ) {
365 - $this->validated = true;
366 - return $user;
367 - }
368 - $error_message = sprintf( '<strong>%s</strong>: %s', 'Error', esc_attr__( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
369 -
370 - if ( $result['reason'] === 'VERIFICATION_FAILED' || $result['reason'] === 'RECAPTCHA_EMPTY_RESPONSE' ) {
371 - wp_clear_auth_cookie();
372 - return new WP_Error( 'patchstack_error', $error_message );
373 - }
374 -
375 - if ( isset( $_REQUEST['log'], $_REQUEST['pwd'] ) ) {
376 - return new WP_Error( 'patchstack_error', $error_message );
377 - }
378 - } else {
379 - $this->validated = true;
380 - return $user;
381 - }
382 - }
383 -
384 - /**
385 - * Check reCAPTCHA upon login.
386 - *
387 - * @param string $user
388 - * @param string $password
389 - * @return WP_User|WP_Error
390 - */
391 - public function login_captcha_check_woocommerce( $error, $username, $password, $email ) {
392 - if ( $this->validated ) {
393 - return $error;
394 - }
395 -
396 - $result = $this->plugin->hardening->captcha_check();
397 -
398 - if ( $result['response'] || $result['reason'] == 'ERROR_NO_KEYS' ) {
399 - $this->validated = true;
400 - return $error;
401 - }
402 -
403 - if ( ! is_wp_error( $error ) ) {
404 - $error = new WP_Error();
405 - }
406 -
407 - $error->add( 'patchstack_error', 'ERROR' . ':&nbsp;' . esc_attr__( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
408 - return $error;
409 - }
410 -
411 - /**
412 - * Captcha check for the register or lost password form.
413 - *
414 - * @param mixed|WP_Error $error
415 - * @return WP_Error
416 - */
417 - public function general_captcha_check( $error ) {
418 - if ( $this->validated ) {
419 - return $error;
420 - }
421 -
422 - $result = $this->plugin->hardening->captcha_check();
423 -
424 - if ( $result['response'] || $result['reason'] == 'ERROR_NO_KEYS' ) {
425 - $this->validated = true;
426 - return $error;
427 - }
428 -
429 - if ( ! is_wp_error( $error ) ) {
430 - $error = new WP_Error();
431 - }
432 -
433 - $error->add( 'patchstack_error', 'ERROR' . ':&nbsp;' . esc_attr__( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
434 - return $error;
435 - }
436 -}
1 +<?php
2 +
3 +// Do not allow the file to be called directly.
4 +if ( ! defined( 'ABSPATH' ) ) {
5 + exit;
6 +}
7 +
8 +/**
9 + * This class is used to alter anything related to the login page.
10 + */
11 +class P_Login extends P_Core {
12 +
13 + /**
14 + * Add the actions required to interact with the login process.
15 + *
16 + * @param Patchstack $core
17 + * @return void
18 + */
19 + public function __construct( $core ) {
20 + parent::__construct( $core );
21 +
22 + if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
23 + return;
24 + }
25 +
26 + add_action( 'login_init', array( $this, 'add_captcha' ) );
27 + add_action( 'login_init', array( $this, 'check_ipban' ) );
28 + add_action( 'login_init', array( $this, 'check_logonhours' ) );
29 + add_action( 'login_head', array( $this, 'add_captcha' ) );
30 + add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ), 1 );
31 +
32 + // 2FA related actions.
33 + if ( $this->get_option( 'patchstack_login_2fa', 0 ) ) {
34 + add_action( 'login_form', array( $this, 'tfa_login_form' ) );
35 + add_action( 'authenticate', array( $this, 'tfa_authenticate' ), 30, 3 );
36 + add_action( 'profile_personal_options', array( $this, 'tfa_profile_personal_options' ) );
37 + add_action( 'personal_options_update', array( $this, 'tfa_personal_options_update' ) );
38 + add_action( 'admin_enqueue_scripts', array( $this, 'tfa_admin_enqueue_scripts' ) );
39 + }
40 + }
41 +
42 + /**
43 + * Register the Google reCAPTCHA JavaScript for the login area.
44 + *
45 + * @return void
46 + */
47 + public function login_enqueue_scripts() {
48 + if ( $this->get_option( 'patchstack_captcha_login_form', false ) && $this->get_option( 'patchstack_captcha_type' ) != 'v3' ) {
49 + wp_enqueue_script( 'patchstack_captcha', 'https://www.google.com/recaptcha/api.js' );
50 + }
51 + }
52 +
53 + /**
54 + * Add the 2FA code to the login form.
55 + *
56 + * @return void
57 + */
58 + public function tfa_login_form() {
59 + require_once dirname( __FILE__ ) . '/views/2fa-login-form.php';
60 + }
61 +
62 + /**
63 + * Check the 2FA code, if 2FA is enabled for the user.
64 + *
65 + * @param object $user
66 + * @param string $username
67 + * @param string $password
68 + * @return object|WP_User|WP_Error
69 + */
70 + public function tfa_authenticate( $user, $username = '', $password = '' ) {
71 + if ( ! isset( $user->ID ) ) {
72 + return $user;
73 + }
74 +
75 + // If we have a valid user object, check to see if the user has 2FA enabled.
76 + $enabled = get_user_option( 'webarx_2fa_enabled', $user->ID );
77 + $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
78 + if ( empty( $enabled ) ) {
79 + return $user;
80 + }
81 +
82 + // If enabled, check to see if the verification code is being sent.
83 + if ( ! isset( $_POST['patchstack_2fa'] ) || ( isset( $_POST['patchstack_2fa'] ) && $_POST['patchstack_2fa'] == '' ) ) {
84 + return new WP_Error( 'patchstack_2fa_empty_code', __( 'Please enter the 2FA authentication code that is generated on your device.', 'patchstack' ) );
85 + }
86 +
87 + // Verify the code.
88 + require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
89 + if ( ! TokenAuth6238::verify( $secret, trim( $_POST['patchstack_2fa'] ) ) ) {
90 + return new WP_Error( 'patchstack_2fa_invalid_code', __( 'The 2FA authentication code you entered is invalid.', 'patchstack' ) );
91 + }
92 +
93 + return $user;
94 + }
95 +
96 + /**
97 + * Show the 2FA fields.
98 + *
99 + * @param object $user
100 + * @return void
101 + */
102 + public function tfa_profile_personal_options( $user ) {
103 + $secret = get_user_option( 'webarx_2fa_secretkey', $user->ID );
104 +
105 + // If user has no secret key set yet, generate one.
106 + if ( empty( $secret ) ) {
107 + require_once dirname( __FILE__ ) . '/2fa/rfc6238.php';
108 + $secret = TokenAuth6238::generateRandomClue();
109 + update_user_option( $user->ID, 'webarx_2fa_secretkey', $secret, true );
110 + }
111 +
112 + require_once dirname( __FILE__ ) . '/views/2fa-profile-configuration.php';
113 + }
114 +
115 + /**
116 + * Update the 2FA fields.
117 + *
118 + * @param integer $user_id
119 + * @return void
120 + */
121 + public function tfa_personal_options_update( $user_id ) {
122 + update_user_option( $user_id, 'webarx_2fa_enabled', ! empty( $_POST['patchstack_2fa_enabled'] ), true );
123 + }
124 +
125 + /**
126 + * Add the QRCode image generator JavaScript library.
127 + *
128 + * @return void
129 + */
130 + public function tfa_admin_enqueue_scripts() {
131 + wp_register_script( 'patchstack_qrcode', $this->plugin->url . '/assets/js/qrcode.min.js', array(), $this->plugin->version );
132 + wp_enqueue_script( 'patchstack_qrcode' );
133 + }
134 +
135 + /**
136 + * Check if the IP address is banned from attempting to guess passwords.
137 + *
138 + * @return void
139 + */
140 + public function check_ipban() {
141 + if ( is_user_logged_in() || ! $this->get_option( 'patchstack_block_bruteforce_ips', 0 ) ) {
142 + return;
143 + }
144 +
145 + // Check if the users IP address is whitelisted.
146 + $ip = $this->get_ip();
147 + if ( $this->plugin->ban->is_ip_whitelisted( $ip ) ) {
148 + return;
149 + }
150 +
151 + // Check if X failed login attempts were made.
152 + global $wpdb;
153 + $results = $wpdb->get_results(
154 + $wpdb->prepare( 'SELECT COUNT(*) AS numIps FROM ' . $wpdb->prefix . "patchstack_event_log WHERE ip = '%s' AND action = 'failed login' AND date >= ('" . current_time( 'mysql' ) . "' - INTERVAL %d MINUTE)", array( $ip, ( $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 ) + $this->get_option( 'patchstack_anti_bruteforce_minutes', 5 ) ) ) ),
155 + OBJECT
156 + );
157 +
158 + // Determine the number of attempts.
159 + if ( ! isset( $results, $results[0], $results[0]->numIps ) ) {
160 + $num = 0;
161 + } else {
162 + $num = $results[0]->numIps;
163 + }
164 +
165 + // Block the user?
166 + if ( $num >= $this->get_option( 'patchstack_anti_bruteforce_attempts', 10 ) ) {
167 + $this->plugin->firewall_base->display_error_page( 22 );
168 + }
169 + }
170 +
171 + /**
172 + * If logon hours are set, check the current time and allow or disallow the user
173 + * to login depending on the settings.
174 + *
175 + * @return void
176 + */
177 + public function check_logonhours() {
178 + if ( ! $this->get_option( 'patchstack_login_time_block', 0 ) || is_user_logged_in() || $this->get_option( 'patchstack_login_time_start', '00:00' ) == $this->get_option( 'patchstack_login_time_end', '23:59' ) ) {
179 + return;
180 + }
181 + $block = true;
182 +
183 + // Current time.
184 + $hour = current_time( 'G' );
185 + $min = current_time( 'i' );
186 + $stamp_current = current_time( 'U' );
187 +
188 + // Get time start.
189 + $start = explode( ':', str_replace( '00', '0', $this->get_option( 'patchstack_login_time_start', '00:00' ) ) );
190 + if ( count( $start ) != 2 ) {
191 + return;
192 + }
193 + $stamp_start = strtotime( current_time( 'Y-m-d' ) . ' ' . $this->get_option( 'patchstack_login_time_start', '00:00' ) . ':00' );
194 + $start[0] = (int) $start[0];
195 + $start[1] = (int) $start[1];
196 +
197 + // Get time end.
198 + $end = explode( ':', str_replace( '00', '0', $this->get_option( 'patchstack_login_time_end', '23:59' ) ) );
199 + if ( count( $end ) != 2 ) {
200 + return;
201 + }
202 + $stamp_end = strtotime( current_time( 'Y-m-d' ) . ' ' . $this->get_option( 'patchstack_login_time_end', '00:00' ) . ':00' );
203 + $end[0] = (int) $end[0];
204 + $end[1] = (int) $end[1];
205 +
206 + // If begin time is earlier than end time.
207 + if ( $start[0] <= $end[0] && $stamp_current >= $stamp_start && $stamp_current <= $stamp_end ) {
208 + $block = false;
209 + }
210 +
211 + // If begin time is later than end time.
212 + if ( $start[0] > $end[0] && ( $hour >= $start[0] || $hour <= $end[0] ) ) {
213 + $block = false;
214 +
215 + if ( ( $hour == $start[0] && $min < $start[1] ) || ( $hour == $end[0] && $min > $end[1] ) ) {
216 + $block = true;
217 + }
218 + }
219 +
220 + // Block the user?
221 + if ( $block ) {
222 + wp_die( __( 'Access to the login page has been restricted due to set logon hours.', 'patchstack' ), __( 'Login Disallowed', 'patchstack' ) );
223 + }
224 + }
225 +
226 + /**
227 + * Determine if we should inject reCAPTCHA into certain pages.
228 + *
229 + * @return void
230 + */
231 + public function add_captcha() {
232 + switch ( $this->get_option( 'patchstack_captcha_type' ) ) {
233 + case 'v2':
234 + $public = $this->get_option( 'patchstack_captcha_public_key', '' );
235 + $private = $this->get_option( 'patchstack_captcha_private_key', '' );
236 + break;
237 + case 'invisible':
238 + $public = $this->get_option( 'patchstack_captcha_public_key_v3', '' );
239 + $private = $this->get_option( 'patchstack_captcha_private_key_v3', '' );
240 + break;
241 + case 'v3':
242 + $public = $this->get_option( 'patchstack_captcha_public_key_v3_new', '' );
243 + $private = $this->get_option( 'patchstack_captcha_private_key_v3_new', '' );
244 + break;
245 + default:
246 + return;
247 + break;
248 + }
249 +
250 + // Make sure that the keys are set.
251 + if ( $public == '' || $private == '' ) {
252 + return;
253 + }
254 +
255 + // reCAPTCHA on the login page.
256 + if ( $this->get_option( 'patchstack_captcha_login_form' ) ) {
257 + add_filter( 'login_form', array( $this->plugin->hardening, 'captcha_display' ) );
258 + add_filter( 'wp_authenticate_user', array( $this, 'login_captcha_check' ), 10, 2 );
259 + }
260 +
261 + // reCAPTCHA on the registration form.
262 + if ( $this->get_option( 'patchstack_captcha_registration_form' ) ) {
263 + add_action( 'register_form', array( $this->plugin->hardening, 'captcha_display' ) );
264 + add_action( 'registration_errors', array( $this, 'general_captcha_check' ) );
265 + }
266 +
267 + // reCAPTCHA on the reset password form.
268 + if ( $this->get_option( 'patchstack_captcha_reset_pwd_form' ) ) {
269 + add_action( 'lostpassword_form', array( $this->plugin->hardening, 'captcha_display' ) );
270 + add_action( 'allow_password_reset', array( $this, 'general_captcha_check' ) );
271 + }
272 + }
273 +
274 + /**
275 + * Check reCAPTCHA upon login.
276 + *
277 + * @param string $user
278 + * @param string $password
279 + * @return WP_User|WP_Error
280 + */
281 + public function login_captcha_check( $user, $password ) {
282 + $result = $this->plugin->hardening->captcha_check();
283 +
284 + if ( ! $result['response'] ) {
285 + if ( $result['reason'] === 'ERROR_NO_KEYS' ) {
286 + return $user;
287 + }
288 + $error_message = sprintf( '<strong>%s</strong>: %s', 'Error', __( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
289 +
290 + if ( $result['reason'] === 'VERIFICATION_FAILED' || $result['reason'] === 'RECAPTCHA_EMPTY_RESPONSE' ) {
291 + wp_clear_auth_cookie();
292 + return new WP_Error( 'patchstack_error', $error_message );
293 + }
294 +
295 + if ( isset( $_REQUEST['log'], $_REQUEST['pwd'] ) ) {
296 + return new WP_Error( 'patchstack_error', $error_message );
297 + }
298 + } else {
299 + return $user;
300 + }
301 + }
302 +
303 + /**
304 + * Captcha check for the register or lost password form.
305 + *
306 + * @param mixed|WP_Error $error
307 + * @return WP_Error
308 + */
309 + public function general_captcha_check( $error ) {
310 + $result = $this->plugin->hardening->captcha_check();
311 +
312 + if ( $result['response'] || $result['reason'] == 'ERROR_NO_KEYS' ) {
313 + return $error;
314 + }
315 +
316 + if ( ! is_wp_error( $error ) ) {
317 + $error = new WP_Error();
318 + }
319 +
320 + $error->add( 'patchstack_error', 'ERROR' . ':&nbsp;' . __( 'You have entered an incorrect reCAPTCHA value. Refresh this page and try again.', 'patchstack' ) );
321 + return $error;
322 + }
323 +}