PluginProbe
Patchstack – WordPress & Plugins Security / 2.1.0
Patchstack – WordPress & Plugins Security v2.1.0
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.0, at includes/login.php

324 lines 10.6 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 // 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 }
324