| @@ -8,17 +8,9 @@ | ||
| 8 | 8 | /** |
| 9 | 9 | * This class is used to hide the login page, if it's enabled. |
| 10 | 10 | */ |
| 11 | 11 | class P_Hide_Login extends P_Core { |
| 12 | - | |
| 13 | 12 | /** |
| 14 | - * Whether we should use PHP to redirect the login. | |
| 15 | - * | |
| 16 | - * @var boolean | |
| 17 | - */ | |
| 18 | - protected $wp_login_php = false; | |
| 19 | - | |
| 20 | - /** | |
| 21 | 13 | * Add the actions required to hide the login page. |
| 22 | 14 | * |
| 23 | 15 | * @param Patchstack $core |
| 24 | 16 | * @return void |
| @@ -29,14 +21,11 @@ | ||
| 29 | 21 | if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) { |
| 30 | 22 | return; |
| 31 | 23 | } |
| 32 | 24 | |
| 33 | - // Determine if the proper WordPress version is installed. | |
| 34 | - global $wp_version; | |
| 35 | - if ( version_compare( $wp_version, '4.0-RC1-src', '<' ) ) { | |
| 36 | - add_action( 'admin_notices', array( $this, 'admin_notices_incompatible' ) ); | |
| 37 | - add_action( 'network_admin_notices', array( $this, 'admin_notices_incompatible' ) ); | |
| 38 | - return; | |
| 25 | + // Update the renamed login page if it's set to our hardcoded one. | |
| 26 | + if ( get_site_option( 'patchstack_mv_wp_login' ) == 0 && get_site_option( 'patchstack_rename_wp_login' ) == 'swlogin' ) { | |
| 27 | + update_site_option( 'patchstack_rename_wp_login', md5( wp_generate_password( 32, true, true ) ) ); | |
| 39 | 28 | } |
| 40 | 29 | |
| 41 | 30 | // No need to continue if it is not enabled. |
| 42 | 31 | if ( ! get_site_option( 'patchstack_mv_wp_login' ) || ! get_site_option( 'patchstack_rename_wp_login' ) ) { |
| @@ -42,287 +31,125 @@ | ||
| 42 | 31 | if ( ! get_site_option( 'patchstack_mv_wp_login' ) || ! get_site_option( 'patchstack_rename_wp_login' ) ) { |
| 43 | 32 | return; |
| 44 | 33 | } |
| 45 | 34 | |
| 46 | - // We need to load the plugin library for multisite. | |
| 47 | - if ( is_multisite() && ( ! function_exists( 'is_plugin_active_for_network' ) || ! function_exists( 'is_plugin_active' ) ) ) { | |
| 48 | - require_once ABSPATH . '/wp-admin/includes/plugin.php'; | |
| 49 | - } | |
| 50 | - | |
| 51 | 35 | // Register the filters and actions for the functionality. |
| 52 | - add_filter( 'site_url', array( $this, 'site_url' ), 10, 4 ); | |
| 53 | - add_filter( 'network_site_url', array( $this, 'network_site_url' ), 10, 3 ); | |
| 54 | - add_filter( 'wp_redirect', array( $this, 'wp_redirect' ), 10, 2 ); | |
| 55 | - add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 9999 ); | |
| 56 | - add_action( 'wp_loaded', array( $this, 'wp_loaded' ) ); | |
| 57 | - add_action( 'init', array( $this, 'deny_default_login_page' ) ); | |
| 36 | + add_action( 'init', [ $this, 'init' ], ~PHP_INT_MAX + 1 ); | |
| 37 | + add_action( 'wp_logout', [ $this, 'wp_logout' ] ); | |
| 58 | 38 | } |
| 59 | 39 | |
| 60 | 40 | /** |
| 61 | - * Deny access to wp-login if rewrite wp-admin option is enabled. | |
| 41 | + * Deny access to wp-login.php if the login page rename feature is enabled. | |
| 62 | 42 | * |
| 63 | 43 | * @return void |
| 64 | 44 | */ |
| 65 | - public function deny_default_login_page() { | |
| 66 | - if ( get_site_option( 'patchstack_mv_wp_login' ) && strpos( strtolower( $_SERVER['REQUEST_URI'] ), 'wp-login.php' ) !== false ) { | |
| 67 | - die( 'Forbidden!' ); | |
| 45 | + public function init() { | |
| 46 | + // Do not block the user if they are already logged in as that would block a logout. | |
| 47 | + if ( is_user_logged_in() ) { | |
| 48 | + return; | |
| 68 | 49 | } |
| 69 | - } | |
| 70 | 50 | |
| 71 | - /** | |
| 72 | - * Send the email that contains the new login page URL. | |
| 73 | - * | |
| 74 | - * @return boolean If the email was sent or not. | |
| 75 | - */ | |
| 76 | - public function send_email() { | |
| 77 | - global $current_user; | |
| 78 | - $subject = __( 'New Login URL', 'patchstack' ); | |
| 79 | - $message = '<br /><br />Your login page is now here: <strong> <a href="' . get_site_url() . '/' . get_site_option( 'patchstack_rename_wp_login' ) . '">' . get_site_url() . '/' . get_site_option( 'patchstack_rename_wp_login' ) . '</strong></a>'; | |
| 80 | - return wp_mail( $current_user->user_email, $subject, $message ); | |
| 81 | - } | |
| 51 | + // Determine if the user is whitelisted. | |
| 52 | + if ( ( stripos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) !== false || $GLOBALS['pagenow'] === 'wp-login.php' || $_SERVER['PHP_SELF'] === '/wp-login.php' ) && ! $this->is_whitelisted() ) { | |
| 53 | + if ( isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], ['confirm_admin_email', 'postpass', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'checkemail', 'confirmaction'] ) ) { | |
| 54 | + return; | |
| 55 | + } | |
| 82 | 56 | |
| 83 | - /** | |
| 84 | - * Get the current site's URL. | |
| 85 | - * | |
| 86 | - * @param string $url | |
| 87 | - * @param string $path | |
| 88 | - * @param string $scheme | |
| 89 | - * @param integer $blog_id | |
| 90 | - * @return string | |
| 91 | - */ | |
| 92 | - public function site_url( $url, $path, $scheme, $blog_id ) { | |
| 93 | - return $this->filter_wp_login_php( $url, $scheme ); | |
| 94 | - } | |
| 95 | - | |
| 96 | - /** | |
| 97 | - * Determine if the current page is a login/registration page. | |
| 98 | - * | |
| 99 | - * @return void | |
| 100 | - */ | |
| 101 | - public function plugins_loaded() { | |
| 102 | - global $pagenow; | |
| 103 | - $stop = false; | |
| 104 | - $request = parse_url( $_SERVER['REQUEST_URI'] ); | |
| 105 | - | |
| 106 | - // If the current page is wp-login.php | |
| 107 | - if ( ( strpos( rawurldecode( $_SERVER['REQUEST_URI'] ), 'wp-login.php' ) !== false || untrailingslashit( $request['path'] ) === site_url( 'wp-login', 'relative' ) ) && ! is_admin() ) { | |
| 108 | - $this->wp_login_php = true; | |
| 109 | - $_SERVER['REQUEST_URI'] = $this->user_trailingslashit( '/' . str_repeat( '-/', 10 ) ); | |
| 110 | - $pagenow = 'index.php'; | |
| 111 | - $stop = true; | |
| 57 | + $this->plugin->firewall_base->display_error_page( 'login' ); | |
| 112 | 58 | } |
| 113 | 59 | |
| 114 | - // If the current page is the renamed login page. | |
| 115 | - if ( ! $stop && ( untrailingslashit( $request['path'] ) === home_url( $this->new_login_slug(), 'relative' ) || ( ! get_site_option( 'permalink_structure' ) && isset( $_GET[ $this->new_login_slug() ] ) && empty( $_GET[ $this->new_login_slug() ] ) ) ) ) { | |
| 116 | - $pagenow = 'wp-login.php'; | |
| 117 | - $stop = true; | |
| 118 | - } | |
| 60 | + // If the current page is the renamed login page we give the user access for 10 minutes to the login page. | |
| 61 | + if ( strpos( $_SERVER['REQUEST_URI'], get_site_option( 'patchstack_rename_wp_login' ) ) !== false ) { | |
| 62 | + // Whitelist the current IP address. | |
| 63 | + $this->whitelist_ip(); | |
| 119 | 64 | |
| 120 | - // If the current page is registration page. | |
| 121 | - if ( ! $stop && ( ( strpos( rawurldecode( $_SERVER['REQUEST_URI'] ), 'wp-register.php' ) !== false || untrailingslashit( $request['path'] ) === site_url( 'wp-register', 'relative' ) ) && ! is_admin() ) ) { | |
| 122 | - $this->wp_login_php = true; | |
| 123 | - $_SERVER['REQUEST_URI'] = $this->user_trailingslashit( '/' . str_repeat( '-/', 10 ) ); | |
| 124 | - $pagenow = 'index.php'; | |
| 125 | - } | |
| 126 | - } | |
| 127 | - | |
| 128 | - /** | |
| 129 | - * Determine if we should redirect the user upon visiting new/old login page. | |
| 130 | - * | |
| 131 | - * @return void | |
| 132 | - */ | |
| 133 | - public function wp_loaded() { | |
| 134 | - global $pagenow; | |
| 135 | - $request = parse_url( $_SERVER['REQUEST_URI'] ); | |
| 136 | - | |
| 137 | - // Redirect when admin page is requested but no admin access. | |
| 138 | - if ( is_admin() && ! is_user_logged_in() && ! defined( 'DOING_AJAX' ) && $pagenow !== 'admin-post.php' && ( isset( $_GET ) && empty( $_GET['adminhash'] ) && $request['path'] !== '/wp-admin/options.php' ) ) { | |
| 139 | - wp_safe_redirect( home_url( '/404' ) ); | |
| 140 | - exit; | |
| 141 | - } | |
| 142 | - | |
| 143 | - // If the current page is the login page, redirect to 404. | |
| 144 | - if ( $pagenow === 'wp-login.php' && $request['path'] !== $this->user_trailingslashit( $request['path'] ) && get_site_option( 'permalink_structure' ) ) { | |
| 145 | - wp_safe_redirect( home_url( '/404' ) ); | |
| 146 | - exit; | |
| 147 | - } | |
| 148 | - | |
| 149 | - // Other possible login pages that we need to block by default. | |
| 150 | - $login_pages = array( | |
| 151 | - home_url( 'wp-login.php', 'relative' ), | |
| 152 | - home_url( 'login', 'relative' ), | |
| 153 | - site_url( 'login', 'relative' ), | |
| 154 | - ); | |
| 155 | - if ( in_array( untrailingslashit( $_SERVER['REQUEST_URI'] ), $login_pages, true ) ) { | |
| 156 | - wp_safe_redirect( home_url( '/404' ) ); | |
| 157 | - exit; | |
| 158 | - } | |
| 159 | - | |
| 160 | - // Determine if we should redirect the user to the new login page. | |
| 161 | - if ( $this->wp_login_php ) { | |
| 162 | - if ( ( $referer = wp_get_referer() ) && strpos( $referer, 'wp-activate.php' ) !== false && ( $referer = parse_url( $referer ) ) && ! empty( $referer['query'] ) ) { | |
| 163 | - parse_str( $referer['query'], $referer ); | |
| 164 | - | |
| 165 | - // When a user is self-created. | |
| 166 | - if ( ! empty( $referer['key'] ) && ( $result = wpmu_activate_signup( $referer['key'] ) ) && is_wp_error( $result ) && ( $result->get_error_code() === 'already_active' || $result->get_error_code() === 'blog_taken' ) ) { | |
| 167 | - wp_safe_redirect( $this->new_login_url() . ( ! empty( $_SERVER['QUERY_STRING'] ) ? '?' . $_SERVER['QUERY_STRING'] : '' ) ); | |
| 168 | - exit; | |
| 169 | - } | |
| 65 | + // Supported by a number of popular caching plugins. | |
| 66 | + if ( ! defined( 'DONOTCACHEPAGE' ) ) { | |
| 67 | + define( 'DONOTCACHEPAGE', true ); | |
| 170 | 68 | } |
| 171 | - $this->wp_template_loader(); | |
| 172 | - } | |
| 173 | 69 | |
| 174 | - @require_once ABSPATH . 'wp-includes/post.php'; | |
| 70 | + // No caching. | |
| 71 | + send_nosniff_header(); | |
| 72 | + nocache_headers(); | |
| 175 | 73 | |
| 176 | - // There must be a better way | |
| 177 | - $one = get_home_url() . '/' . $request['path'] . '/'; | |
| 178 | - $two = get_home_url() . '/' . get_site_option( 'patchstack_rename_wp_login' ) . '/'; | |
| 179 | - $one = str_replace( get_home_url(), '', $one ); | |
| 180 | - $two_url = parse_url( get_home_url() ); | |
| 181 | - $two = str_replace( $two_url['scheme'] . '://' . $two_url['host'], '', $two ); | |
| 182 | - $one = str_replace( '//', '/', $one ); | |
| 183 | - $two = str_replace( '//', '/', $two ); | |
| 184 | - | |
| 185 | - // Show the login page if not already logged in. | |
| 186 | - if ( $one == $two ) { | |
| 187 | - global $error, $interim_login, $action, $user_login; | |
| 188 | - | |
| 189 | - if ( is_user_logged_in() && ! isset( $_REQUEST['action'] ) ) { | |
| 190 | - wp_safe_redirect( admin_url() ); | |
| 191 | - exit; | |
| 192 | - } | |
| 193 | - @require_once ABSPATH . 'wp-login.php'; | |
| 74 | + // User should be whitelisted now, redirect to the login page. | |
| 75 | + wp_safe_redirect( 'wp-login.php', 307 ); | |
| 194 | 76 | exit; |
| 195 | 77 | } |
| 196 | 78 | } |
| 197 | 79 | |
| 198 | 80 | /** |
| 199 | - * Load the template loader. | |
| 200 | - * | |
| 81 | + * If the user is logging out, whitelist them again so they don't see a blocked page. | |
| 82 | + * | |
| 201 | 83 | * @return void |
| 202 | 84 | */ |
| 203 | - private function wp_template_loader() { | |
| 204 | - global $pagenow; | |
| 205 | - $pagenow = 'index.php'; | |
| 206 | - | |
| 207 | - if ( ! defined( 'WP_USE_THEMES' ) ) { | |
| 208 | - define( 'WP_USE_THEMES', true ); | |
| 209 | - } | |
| 210 | - | |
| 211 | - wp(); | |
| 212 | - if ( $_SERVER['REQUEST_URI'] === $this->user_trailingslashit( str_repeat( '-/', 10 ) ) ) { | |
| 213 | - $_SERVER['REQUEST_URI'] = $this->user_trailingslashit( '/wp-login-php/' ); | |
| 214 | - } | |
| 215 | - | |
| 216 | - @require_once ABSPATH . WPINC . '/template-loader.php'; | |
| 217 | - exit; | |
| 85 | + public function wp_logout() { | |
| 86 | + $this->whitelist_ip(); | |
| 218 | 87 | } |
| 219 | 88 | |
| 220 | 89 | /** |
| 221 | - * Filter the wp-login.php URL. | |
| 222 | - * | |
| 223 | - * @param string $url | |
| 224 | - * @param string $scheme | |
| 225 | - * @return string | |
| 90 | + * Determine if the IP address is whitelisted. | |
| 91 | + * | |
| 92 | + * @return boolean | |
| 226 | 93 | */ |
| 227 | - public function filter_wp_login_php( $url, $scheme = null ) { | |
| 228 | - // Attempt to retrieve the URL. | |
| 229 | - if ( strpos( $url, 'wp-login.php' ) !== false ) { | |
| 230 | - if ( is_ssl() ) { | |
| 231 | - $scheme = 'https'; | |
| 232 | - } | |
| 94 | + private function is_whitelisted() { | |
| 95 | + // Process the whitelist, and remove old ones. | |
| 96 | + $whitelist = get_site_option( 'patchstack_rename_wp_login_whitelist', [] ); | |
| 97 | + $new_whitelist = []; | |
| 98 | + $allow = false; | |
| 233 | 99 | |
| 234 | - $args = explode( '?', $url ); | |
| 235 | - if ( isset( $args[1] ) ) { | |
| 236 | - parse_str( $args[1], $args ); | |
| 237 | - if ( isset( $args['login'] ) ) { | |
| 238 | - $args['login'] = rawurlencode( $args['login'] ); | |
| 100 | + // Only continue if there are actually any whitelist entries. | |
| 101 | + if ( is_array( $whitelist ) && count( $whitelist ) != 0 ){ | |
| 102 | + $ip = $this->get_ip(); | |
| 103 | + foreach ( $whitelist as $entry ) { | |
| 104 | + | |
| 105 | + // Determine if the whitelist entry is still valid. | |
| 106 | + if ( ( time() - $entry[1] ) <= 600 ) { | |
| 107 | + array_push( $new_whitelist, $entry ); | |
| 108 | + | |
| 109 | + // Determine if the IP address matches. | |
| 110 | + if ( $ip === $entry[0] ) { | |
| 111 | + $allow = true; | |
| 112 | + } | |
| 239 | 113 | } |
| 240 | - $url = add_query_arg( $args, $this->new_login_url( $scheme ) ); | |
| 241 | - } else { | |
| 242 | - $url = $this->new_login_url( $scheme ); | |
| 243 | 114 | } |
| 115 | + | |
| 116 | + update_site_option( 'patchstack_rename_wp_login_whitelist', $new_whitelist ); | |
| 244 | 117 | } |
| 245 | 118 | |
| 246 | - return $url; | |
| 119 | + return $allow; | |
| 247 | 120 | } |
| 248 | 121 | |
| 249 | 122 | /** |
| 250 | - * Get the network site URL. | |
| 251 | - * | |
| 252 | - * @param string $url | |
| 253 | - * @param string $path | |
| 254 | - * @param string $scheme | |
| 255 | - * @return string | |
| 123 | + * Whitelist the current IP address, or extend the time. | |
| 124 | + * | |
| 125 | + * @return void | |
| 256 | 126 | */ |
| 257 | - public function network_site_url( $url, $path, $scheme ) { | |
| 258 | - return $this->filter_wp_login_php( $url, $scheme ); | |
| 259 | - } | |
| 127 | + private function whitelist_ip() { | |
| 128 | + $whitelist = get_site_option( 'patchstack_rename_wp_login_whitelist', [] ); | |
| 129 | + $new_whitelist = []; | |
| 260 | 130 | |
| 261 | - /** | |
| 262 | - * Redirect the user to given location. | |
| 263 | - * | |
| 264 | - * @param string $location | |
| 265 | - * @param integer $status | |
| 266 | - * @return string | |
| 267 | - */ | |
| 268 | - public function wp_redirect( $location, $status ) { | |
| 269 | - return $this->filter_wp_login_php( $location ); | |
| 270 | - } | |
| 131 | + // If the IP address is already whitelisted, reset the timestamp. | |
| 132 | + if ( is_array( $whitelist ) && count( $whitelist ) != 0 ) { | |
| 133 | + $ip = $this->get_ip(); | |
| 134 | + $whitelisted = false; | |
| 135 | + foreach ( $whitelist as $entry ) { | |
| 136 | + // Determine if we should extend the whitelist time or ignore if already whitelisted. | |
| 137 | + if ( $ip === $entry[0] ) { | |
| 138 | + $new_whitelist[] = [ $ip, time() ]; | |
| 139 | + $whitelisted = true; | |
| 140 | + } else { | |
| 141 | + $new_whitelist[] = $entry; | |
| 142 | + } | |
| 143 | + } | |
| 271 | 144 | |
| 272 | - /** | |
| 273 | - * Show a notice that WordPress needs to be upgraded. | |
| 274 | - * | |
| 275 | - * @return void | |
| 276 | - */ | |
| 277 | - public function admin_notices_incompatible() { | |
| 278 | - echo wp_kses( '<div class="error notice is-dismissible"><p>' . __( 'Patchstack: Please upgrade to the latest version of WordPress to activate', 'patchstack' ) . '</p></div>', $this->allowed_html ); | |
| 279 | - } | |
| 145 | + // Whitelist the IP address. | |
| 146 | + if ( ! $whitelisted ) { | |
| 147 | + $new_whitelist[] = [ $ip, time() ]; | |
| 148 | + } | |
| 280 | 149 | |
| 281 | - /** | |
| 282 | - * Get the new login slug. | |
| 283 | - * | |
| 284 | - * @return string | |
| 285 | - */ | |
| 286 | - private function new_login_slug() { | |
| 287 | - if ( $slug = get_site_option( 'patchstack_rename_wp_login' ) ) { | |
| 288 | - return $slug; | |
| 289 | - } elseif ( ( is_multisite() && is_plugin_active_for_network( plugin_basename( __FILE__ ) ) && ( $slug = get_site_option( 'patchstack_rename_wp_login', 'login' ) ) ) ) { | |
| 290 | - return $slug; | |
| 291 | - } elseif ( $slug = 'login' ) { | |
| 292 | - return $slug; | |
| 293 | - } | |
| 294 | - } | |
| 295 | - | |
| 296 | - /** | |
| 297 | - * Get the new login URL. | |
| 298 | - * | |
| 299 | - * @param string $scheme | |
| 300 | - * @return void | |
| 301 | - */ | |
| 302 | - public function new_login_url( $scheme = null ) { | |
| 303 | - if ( get_site_option( 'permalink_structure' ) ) { | |
| 304 | - return $this->user_trailingslashit( home_url( '/', $scheme ) . $this->new_login_slug() ); | |
| 150 | + update_site_option( 'patchstack_rename_wp_login_whitelist', $new_whitelist ); | |
| 305 | 151 | } else { |
| 306 | - return home_url( '/', $scheme ) . $this->new_login_slug(); | |
| 152 | + update_site_option( 'patchstack_rename_wp_login_whitelist', [ [ $this->get_ip(), time() ] ] ); | |
| 307 | 153 | } |
| 308 | - } | |
| 309 | - | |
| 310 | - /** | |
| 311 | - * Determine if we have to use trailing slashes. | |
| 312 | - * | |
| 313 | - * @return boolean | |
| 314 | - */ | |
| 315 | - private function use_trailing_slashes() { | |
| 316 | - return substr( get_site_option( 'permalink_structure' ), - 1, 1 ) === '/'; | |
| 317 | - } | |
| 318 | - | |
| 319 | - /** | |
| 320 | - * Use trailing slashes, if needed. | |
| 321 | - * | |
| 322 | - * @param string $string | |
| 323 | - * @return void | |
| 324 | - */ | |
| 325 | - private function user_trailingslashit( $string ) { | |
| 326 | - return $this->use_trailing_slashes() ? trailingslashit( $string ) : untrailingslashit( $string ); | |
| 327 | 154 | } |
| 328 | 155 | } |