PluginProbe
Patchstack – WordPress & Plugins Security / 2.1.14
Patchstack – WordPress & Plugins Security v2.1.14
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
patchstack / includes / login.php

login.php in Patchstack – WordPress & Plugins Security 2.1.14, at includes/login.php

333 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 // Calculate block time.
152 $minutes = (int) $this->get_option( 'patchstack_anti_bruteforce_minutes', 30 );
153 $timeout = (int) $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 );
154 if ( empty( $minutes ) || empty( $timeout ) ) {
155 $time = 30 + 60;
156 } else {
157 $time = $minutes + $timeout;
158 }
159
160 // Check if X failed login attempts were made.
161 global $wpdb;
162 $results = $wpdb->get_results(
163 $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, $time ) ),
164 OBJECT
165 );
166
167 // Determine the number of attempts.
168 if ( ! isset( $results, $results[0], $results[0]->numIps ) ) {
169 $num = 0;
170 } else {
171 $num = $results[0]->numIps;
172 }
173
174 // Block the user?
175 if ( $num >= $this->get_option( 'patchstack_anti_bruteforce_attempts', 10 ) ) {
176 $this->plugin->firewall_base->display_error_page( 22 );
177 }
178 }
179
180 /**
181 * If logon hours are set, check the current time and allow or disallow the user
182 * to login depending on the settings.
183 *
184 * @return void
185 */
186 public function check_logonhours() {
187 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' ) ) {
188 return;
189 }
190 $block = true;
191
192 // Current time.
193 $hour = current_time( 'G' );
194 $min = current_time( 'i' );
195 $stamp_current = current_time( 'U' );
196
197 // Get time start.
198 $start = explode( ':', str_replace( '00', '0', $this->get_option( 'patchstack_login_time_start', '00:00' ) ) );
199 if ( count( $start ) != 2 ) {
200 return;
201 }
202 $stamp_start = strtotime( current_time( 'Y-m-d' ) . ' ' . $this->get_option( 'patchstack_login_time_start', '00:00' ) . ':00' );
203 $start[0] = (int) $start[0];
204 $start[1] = (int) $start[1];
205
206 // Get time end.
207 $end = explode( ':', str_replace( '00', '0', $this->get_option( 'patchstack_login_time_end', '23:59' ) ) );
208 if ( count( $end ) != 2 ) {
209 return;
210 }
211 $stamp_end = strtotime( current_time( 'Y-m-d' ) . ' ' . $this->get_option( 'patchstack_login_time_end', '00:00' ) . ':00' );
212 $end[0] = (int) $end[0];
213 $end[1] = (int) $end[1];
214
215 // If begin time is earlier than end time.
216 if ( $start[0] <= $end[0] && $stamp_current >= $stamp_start && $stamp_current <= $stamp_end ) {
217 $block = false;
218 }
219
220 // If begin time is later than end time.
221 if ( $start[0] > $end[0] && ( $hour >= $start[0] || $hour <= $end[0] ) ) {
222 $block = false;
223
224 if ( ( $hour == $start[0] && $min < $start[1] ) || ( $hour == $end[0] && $min > $end[1] ) ) {
225 $block = true;
226 }
227 }
228
229 // Block the user?
230 if ( $block ) {
231 wp_die( __( 'Access to the login page has been restricted due to set logon hours.', 'patchstack' ), __( 'Login Disallowed', 'patchstack' ) );
232 }
233 }
234
235 /**
236 * Determine if we should inject reCAPTCHA into certain pages.
237 *
238 * @return void
239 */
240 public function add_captcha() {
241 switch ( $this->get_option( 'patchstack_captcha_type' ) ) {
242 case 'v2':
243 $public = $this->get_option( 'patchstack_captcha_public_key', '' );
244 $private = $this->get_option( 'patchstack_captcha_private_key', '' );
245 break;
246 case 'invisible':
247 $public = $this->get_option( 'patchstack_captcha_public_key_v3', '' );
248 $private = $this->get_option( 'patchstack_captcha_private_key_v3', '' );
249 break;
250 case 'v3':
251 $public = $this->get_option( 'patchstack_captcha_public_key_v3_new', '' );
252 $private = $this->get_option( 'patchstack_captcha_private_key_v3_new', '' );
253 break;
254 default:
255 return;
256 break;
257 }
258
259 // Make sure that the keys are set.
260 if ( $public == '' || $private == '' ) {
261 return;
262 }
263
264 // reCAPTCHA on the login page.
265 if ( $this->get_option( 'patchstack_captcha_login_form' ) ) {
266 add_filter( 'login_form', array( $this->plugin->hardening, 'captcha_display' ) );
267 add_filter( 'wp_authenticate_user', array( $this, 'login_captcha_check' ), 10, 2 );
268 }
269
270 // reCAPTCHA on the registration form.
271 if ( $this->get_option( 'patchstack_captcha_registration_form' ) ) {
272 add_action( 'register_form', array( $this->plugin->hardening, 'captcha_display' ) );
273 add_action( 'registration_errors', array( $this, 'general_captcha_check' ) );
274 }
275
276 // reCAPTCHA on the reset password form.
277 if ( $this->get_option( 'patchstack_captcha_reset_pwd_form' ) ) {
278 add_action( 'lostpassword_form', array( $this->plugin->hardening, 'captcha_display' ) );
279 add_action( 'allow_password_reset', array( $this, 'general_captcha_check' ) );
280 }
281 }
282
283 /**
284 * Check reCAPTCHA upon login.
285 *
286 * @param string $user
287 * @param string $password
288 * @return WP_User|WP_Error
289 */
290 public function login_captcha_check( $user, $password ) {
291 $result = $this->plugin->hardening->captcha_check();
292
293 if ( ! $result['response'] ) {
294 if ( $result['reason'] === 'ERROR_NO_KEYS' ) {
295 return $user;
296 }
297 $error_message = sprintf( '<strong>%s</strong>: %s', 'Error', __( 'You have entered an incorrect reCAPTCHA value.', 'patchstack' ) );
298
299 if ( $result['reason'] === 'VERIFICATION_FAILED' || $result['reason'] === 'RECAPTCHA_EMPTY_RESPONSE' ) {
300 wp_clear_auth_cookie();
301 return new WP_Error( 'patchstack_error', $error_message );
302 }
303
304 if ( isset( $_REQUEST['log'], $_REQUEST['pwd'] ) ) {
305 return new WP_Error( 'patchstack_error', $error_message );
306 }
307 } else {
308 return $user;
309 }
310 }
311
312 /**
313 * Captcha check for the register or lost password form.
314 *
315 * @param mixed|WP_Error $error
316 * @return WP_Error
317 */
318 public function general_captcha_check( $error ) {
319 $result = $this->plugin->hardening->captcha_check();
320
321 if ( $result['response'] || $result['reason'] == 'ERROR_NO_KEYS' ) {
322 return $error;
323 }
324
325 if ( ! is_wp_error( $error ) ) {
326 $error = new WP_Error();
327 }
328
329 $error->add( 'patchstack_error', 'ERROR' . ':&nbsp;' . __( 'You have entered an incorrect reCAPTCHA value. Refresh this page and try again.', 'patchstack' ) );
330 return $error;
331 }
332 }
333