From 81e92391ffdef996f980f0e8902168f993411bc9 Mon Sep 17 00:00:00 2001 From: John-Luke McCarthy Date: Thu, 17 Jul 2025 19:11:24 -0500 Subject: [PATCH 1/3] updated version --- pressable-basic-authentication.php | 341 ----------------------------- readme.txt | 2 +- 2 files changed, 1 insertion(+), 342 deletions(-) delete mode 100644 pressable-basic-authentication.php diff --git a/pressable-basic-authentication.php b/pressable-basic-authentication.php deleted file mode 100644 index 0251583..0000000 --- a/pressable-basic-authentication.php +++ /dev/null @@ -1,341 +0,0 @@ -is_ajax_request() ) { - return; - } - - // Skip if we're doing CRON. - if ( $this->is_cron_request() ) { - return; - } - - // Skip if we're in CLI mode. - if ( $this->is_cli_request() ) { - return; - } - - // Skip requests to excluded endpoints - if ($this->should_skip_auth()) { - return; - } - - // Handle logout request. - if ( isset( $_GET['basic-auth-logout'] ) ) { - $this->handle_basic_auth_logout(); - } - - // Redirect from wp-login.php when already authenticated via Basic Auth - $this->maybe_redirect_from_login_page(); - - // Force authentication. - $this->force_basic_authentication(); - } - - /** - * Force Basic Authentication - */ - private function force_basic_authentication() { - // Prevent caching of authentication requests. - $this->prevent_caching(); - - // Extract credentials from headers. - $this->extract_basic_auth_credentials(); - - // Allow Super Admins to bypass authentication. - if ( is_multisite() && is_super_admin() ) { - return; - } - - // Check if the user is already logged in. - if ( is_user_logged_in() ) { - return; - } - - // Check for Basic Authentication credentials. - $auth_user = isset( $_SERVER['PHP_AUTH_USER'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ) : null; - $auth_pass = isset( $_SERVER['PHP_AUTH_PW'] ) ? $_SERVER['PHP_AUTH_PW'] : null; - - if ( ! $auth_user || ! $auth_pass ) { - $this->log_failed_auth( 'Missing credentials' ); - $this->send_auth_headers(); - } - - // Validate credentials against WordPress users table. - $user = wp_authenticate( $auth_user, $auth_pass ); - if ( is_wp_error( $user ) ) { - $this->log_failed_auth( "Invalid credentials for user: $auth_user" ); - $this->send_auth_headers(); - } - - // Log the user in programmatically. - wp_set_current_user( $user->ID ); - wp_set_auth_cookie( $user->ID ); - } - - /** - * Logs failed authentication attempts to the error log. - * - * @param string $message The message to log. - */ - private function log_failed_auth( $message ) { - error_log( - sprintf( - '[%s] Basic Auth Failed: %s', - gmdate( 'Y-m-d H:i:s' ), - $message - ) - ); - } - - /** - * Check if the current request should skip authentication - * - * @return bool - */ - private function should_skip_auth() { - // List of endpoints to exclude from Basic Auth - $excluded_endpoints = array( - 'xmlrpc.php', - 'wp-json/jetpack', - 'wp-json/wp/v2', - 'wp-json/wp/v3' - ); - - // Get current request details - $request_uri = $_SERVER['REQUEST_URI'] ?? ''; - $script_name = $_SERVER['SCRIPT_NAME'] ?? ''; - - // Check if this is a direct xmlrpc.php request - if (basename($script_name) === 'xmlrpc.php') { - return true; - } - - // Check all excluded endpoints - foreach ($excluded_endpoints as $endpoint) { - if (strpos($request_uri, $endpoint) !== false) { - return true; - } - } - - // Check WordPress constants - if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) { - return true; - } - - if (defined('REST_REQUEST') && REST_REQUEST) { - return true; - } - - return false; - } - - /** - * Sends authentication headers. - */ - private function send_auth_headers() { - header( 'WWW-Authenticate: Basic realm="Restricted Area"' ); - header( 'HTTP/1.1 401 Unauthorized' ); - echo '

' . esc_html__( 'Authentication Required', 'pressable-basic-auth' ) . '

'; - exit; - } - - /** - * Use getallheaders() for Servers That Strip Authorization Headers - */ - private function extract_basic_auth_credentials() { - if ( ! empty( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_PW'] ) ) { - return; - } - - // Attempt to fetch credentials from Authorization header. - $auth_header = $this->get_authorization_header(); - - if ( ! $auth_header ) { - return; - } - - if ( 0 === stripos( $auth_header, 'basic ' ) ) { - $auth_encoded = substr( $auth_header, 6 ); - $auth_decoded = base64_decode( $auth_encoded ); - if ( $auth_decoded && strpos( $auth_decoded, ':' ) !== false ) { - list( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) = explode( ':', $auth_decoded, 2 ); - } - } - } - - /** - * Get the authorization header - * - * @return string|null The authorization header value or null - */ - private function get_authorization_header() { - if ( function_exists( 'getallheaders' ) ) { - $headers = getallheaders(); - - // Check for Authorization header (case-insensitive). - foreach ( $headers as $key => $value ) { - if ( strtolower( $key ) === 'authorization' ) { - return $value; - } - } - } - - // Try common alternative locations. - if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) { - return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); - } elseif ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) { - return wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ); - } - - return null; - } - - /** - * Handles Basic Auth logout by forcing a 401 response and then redirecting. - */ - private function handle_basic_auth_logout() { - wp_logout(); // Log out from WordPress. - - // Clear Basic Auth credentials by forcing a 401. - header( 'WWW-Authenticate: Basic realm="Restricted Area"' ); - header( 'HTTP/1.1 401 Unauthorized' ); - - // Output a JavaScript-based redirect after the 401 response. - echo ''; - - // End execution to prevent further processing. - exit; - } - - /** - * Modifies the default WordPress logout URL to trigger Basic Auth logout. - * - * @param string $logout_url The WordPress logout URL. - * @param string $redirect The redirect URL after logout. - * @return string Modified logout URL - */ - public function modify_logout_url( $logout_url, $redirect ) { - return add_query_arg( 'basic-auth-logout', '1', $logout_url ); - } - - /** - * Redirects from wp-login.php to home page when user is already authenticated via Basic Auth - */ - public function maybe_redirect_from_login_page() { - global $pagenow; - - // Check if we're on the login page and have Basic Auth credentials - if ( 'wp-login.php' === $pagenow && - ! empty( $_SERVER['PHP_AUTH_USER'] ) && - ! empty( $_SERVER['PHP_AUTH_PW'] ) && - ! isset( $_GET['action'] ) && - ! isset( $_GET['loggedout'] ) && - ! isset( $_POST['log'] ) ) { - - // Get appropriate home URL for either multisite or regular WordPress - if ( is_multisite() ) { - $redirect_url = network_home_url(); - - // If we can determine the current blog, go to its home instead - if ( isset( $_SERVER['HTTP_HOST'] ) ) { - $blog_details = get_blog_details( array( 'domain' => $_SERVER['HTTP_HOST'] ) ); - if ( $blog_details ) { - $redirect_url = get_home_url( $blog_details->blog_id ); - } - } - } else { - $redirect_url = home_url(); - } - - // Safe redirect - wp_safe_redirect( $redirect_url ); - exit; - } - } - - /** - * Prevent caching of authentication requests - */ - private function prevent_caching() { - header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); - header( 'Pragma: no-cache' ); - header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' ); - } - - /** - * Check if the current request is an AJAX request - * - * @return bool - */ - private function is_ajax_request() { - return ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || - ( ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && 'xmlhttprequest' === strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) ); - } - - /** - * Check if the current request is a cron request - * - * @return bool - */ - private function is_cron_request() { - return defined( 'DOING_CRON' ) && DOING_CRON; - } - - /** - * Check if the current request is a CLI request - * - * @return bool - */ - private function is_cli_request() { - return ( 'cli' === php_sapi_name() || ( defined( 'WP_CLI' ) && WP_CLI ) ); - } -} - -// Initialize the plugin. -new Pressable_Basic_Auth(); \ No newline at end of file diff --git a/readme.txt b/readme.txt index dab5cc2..9f457d5 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: pressable, basic auth, authentication, security Requires at least: 6.7 Tested up to: 6.8 Requires PHP: 8.1 -Stable tag: 1.0.1 +Stable tag: 1.0.2 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 481269b983249b28610f897d7a53aac804e3b512 Mon Sep 17 00:00:00 2001 From: John-Luke McCarthy Date: Thu, 17 Jul 2025 19:40:18 -0500 Subject: [PATCH 2/3] fixed version --- .../pressable-basic-authentication.php | 341 ++++++++++++++++++ pressable-basic-authentication/readme.txt | 43 +++ 2 files changed, 384 insertions(+) create mode 100644 pressable-basic-authentication/pressable-basic-authentication.php create mode 100644 pressable-basic-authentication/readme.txt diff --git a/pressable-basic-authentication/pressable-basic-authentication.php b/pressable-basic-authentication/pressable-basic-authentication.php new file mode 100644 index 0000000..22cee88 --- /dev/null +++ b/pressable-basic-authentication/pressable-basic-authentication.php @@ -0,0 +1,341 @@ +is_ajax_request() ) { + return; + } + + // Skip if we're doing CRON. + if ( $this->is_cron_request() ) { + return; + } + + // Skip if we're in CLI mode. + if ( $this->is_cli_request() ) { + return; + } + + // Skip requests to excluded endpoints + if ($this->should_skip_auth()) { + return; + } + + // Handle logout request. + if ( isset( $_GET['basic-auth-logout'] ) ) { + $this->handle_basic_auth_logout(); + } + + // Redirect from wp-login.php when already authenticated via Basic Auth + $this->maybe_redirect_from_login_page(); + + // Force authentication. + $this->force_basic_authentication(); + } + + /** + * Force Basic Authentication + */ + private function force_basic_authentication() { + // Prevent caching of authentication requests. + $this->prevent_caching(); + + // Extract credentials from headers. + $this->extract_basic_auth_credentials(); + + // Allow Super Admins to bypass authentication. + if ( is_multisite() && is_super_admin() ) { + return; + } + + // Check if the user is already logged in. + if ( is_user_logged_in() ) { + return; + } + + // Check for Basic Authentication credentials. + $auth_user = isset( $_SERVER['PHP_AUTH_USER'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ) : null; + $auth_pass = isset( $_SERVER['PHP_AUTH_PW'] ) ? $_SERVER['PHP_AUTH_PW'] : null; + + if ( ! $auth_user || ! $auth_pass ) { + $this->log_failed_auth( 'Missing credentials' ); + $this->send_auth_headers(); + } + + // Validate credentials against WordPress users table. + $user = wp_authenticate( $auth_user, $auth_pass ); + if ( is_wp_error( $user ) ) { + $this->log_failed_auth( "Invalid credentials for user: $auth_user" ); + $this->send_auth_headers(); + } + + // Log the user in programmatically. + wp_set_current_user( $user->ID ); + wp_set_auth_cookie( $user->ID ); + } + + /** + * Logs failed authentication attempts to the error log. + * + * @param string $message The message to log. + */ + private function log_failed_auth( $message ) { + error_log( + sprintf( + '[%s] Basic Auth Failed: %s', + gmdate( 'Y-m-d H:i:s' ), + $message + ) + ); + } + + /** + * Check if the current request should skip authentication + * + * @return bool + */ + private function should_skip_auth() { + // List of endpoints to exclude from Basic Auth + $excluded_endpoints = array( + 'xmlrpc.php', + 'wp-json/jetpack', + 'wp-json/wp/v2', + 'wp-json/wp/v3' + ); + + // Get current request details + $request_uri = $_SERVER['REQUEST_URI'] ?? ''; + $script_name = $_SERVER['SCRIPT_NAME'] ?? ''; + + // Check if this is a direct xmlrpc.php request + if (basename($script_name) === 'xmlrpc.php') { + return true; + } + + // Check all excluded endpoints + foreach ($excluded_endpoints as $endpoint) { + if (strpos($request_uri, $endpoint) !== false) { + return true; + } + } + + // Check WordPress constants + if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) { + return true; + } + + if (defined('REST_REQUEST') && REST_REQUEST) { + return true; + } + + return false; + } + + /** + * Sends authentication headers. + */ + private function send_auth_headers() { + header( 'WWW-Authenticate: Basic realm="Restricted Area"' ); + header( 'HTTP/1.1 401 Unauthorized' ); + echo '

' . esc_html__( 'Authentication Required', 'pressable-basic-auth' ) . '

'; + exit; + } + + /** + * Use getallheaders() for Servers That Strip Authorization Headers + */ + private function extract_basic_auth_credentials() { + if ( ! empty( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_PW'] ) ) { + return; + } + + // Attempt to fetch credentials from Authorization header. + $auth_header = $this->get_authorization_header(); + + if ( ! $auth_header ) { + return; + } + + if ( 0 === stripos( $auth_header, 'basic ' ) ) { + $auth_encoded = substr( $auth_header, 6 ); + $auth_decoded = base64_decode( $auth_encoded ); + if ( $auth_decoded && strpos( $auth_decoded, ':' ) !== false ) { + list( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) = explode( ':', $auth_decoded, 2 ); + } + } + } + + /** + * Get the authorization header + * + * @return string|null The authorization header value or null + */ + private function get_authorization_header() { + if ( function_exists( 'getallheaders' ) ) { + $headers = getallheaders(); + + // Check for Authorization header (case-insensitive). + foreach ( $headers as $key => $value ) { + if ( strtolower( $key ) === 'authorization' ) { + return $value; + } + } + } + + // Try common alternative locations. + if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) { + return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); + } elseif ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) { + return wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ); + } + + return null; + } + + /** + * Handles Basic Auth logout by forcing a 401 response and then redirecting. + */ + private function handle_basic_auth_logout() { + wp_logout(); // Log out from WordPress. + + // Clear Basic Auth credentials by forcing a 401. + header( 'WWW-Authenticate: Basic realm="Restricted Area"' ); + header( 'HTTP/1.1 401 Unauthorized' ); + + // Output a JavaScript-based redirect after the 401 response. + echo ''; + + // End execution to prevent further processing. + exit; + } + + /** + * Modifies the default WordPress logout URL to trigger Basic Auth logout. + * + * @param string $logout_url The WordPress logout URL. + * @param string $redirect The redirect URL after logout. + * @return string Modified logout URL + */ + public function modify_logout_url( $logout_url, $redirect ) { + return add_query_arg( 'basic-auth-logout', '1', $logout_url ); + } + + /** + * Redirects from wp-login.php to home page when user is already authenticated via Basic Auth + */ + public function maybe_redirect_from_login_page() { + global $pagenow; + + // Check if we're on the login page and have Basic Auth credentials + if ( 'wp-login.php' === $pagenow && + ! empty( $_SERVER['PHP_AUTH_USER'] ) && + ! empty( $_SERVER['PHP_AUTH_PW'] ) && + ! isset( $_GET['action'] ) && + ! isset( $_GET['loggedout'] ) && + ! isset( $_POST['log'] ) ) { + + // Get appropriate home URL for either multisite or regular WordPress + if ( is_multisite() ) { + $redirect_url = network_home_url(); + + // If we can determine the current blog, go to its home instead + if ( isset( $_SERVER['HTTP_HOST'] ) ) { + $blog_details = get_blog_details( array( 'domain' => $_SERVER['HTTP_HOST'] ) ); + if ( $blog_details ) { + $redirect_url = get_home_url( $blog_details->blog_id ); + } + } + } else { + $redirect_url = home_url(); + } + + // Safe redirect + wp_safe_redirect( $redirect_url ); + exit; + } + } + + /** + * Prevent caching of authentication requests + */ + private function prevent_caching() { + header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); + header( 'Pragma: no-cache' ); + header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' ); + } + + /** + * Check if the current request is an AJAX request + * + * @return bool + */ + private function is_ajax_request() { + return ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || + ( ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && 'xmlhttprequest' === strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) ); + } + + /** + * Check if the current request is a cron request + * + * @return bool + */ + private function is_cron_request() { + return defined( 'DOING_CRON' ) && DOING_CRON; + } + + /** + * Check if the current request is a CLI request + * + * @return bool + */ + private function is_cli_request() { + return ( 'cli' === php_sapi_name() || ( defined( 'WP_CLI' ) && WP_CLI ) ); + } +} + +// Initialize the plugin. +new Pressable_Basic_Auth(); \ No newline at end of file diff --git a/pressable-basic-authentication/readme.txt b/pressable-basic-authentication/readme.txt new file mode 100644 index 0000000..9f457d5 --- /dev/null +++ b/pressable-basic-authentication/readme.txt @@ -0,0 +1,43 @@ +=== Pressable Basic Authentication === +Contributors: pressable +Tags: pressable, basic auth, authentication, security +Requires at least: 6.7 +Tested up to: 6.8 +Requires PHP: 8.1 +Stable tag: 1.0.2 +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +== Description == + +The Pressable Basic Authentication plugin enforces HTTP Basic Authentication across your WordPress site, requiring users to authenticate before accessing any page. This is particularly useful for development environments, ensuring that only authorized users can view or interact with the site during development or testing phases.​ + +== FEATURES ==​ + +* Enforces HTTP Basic Authentication on all front-end and back-end pages. +* Installed on sites to restrict public access. +* Allows super administrators to bypass authentication for seamless management. +* Integrates with WordPress's authentication system for user verification. +* Provides a mechanism to log out of Basic Authentication sessions.​ + +== Frequently Asked Questions ==​ + += How do I use this plugin? =​ + +No manual configuration is required. When accessing a protected site, you'll be prompted to enter your WordPress credentials.​ + += Can I disable Basic Authentication on my site? =​ + +Basic Authentication is enforced on sites to protect your site during development. To remove this protection, you can promote your site to a live environment through the MyPressable Control Panel.​ + += What credentials should I use to authenticate? =​ + +Use your WordPress username and password associated with the site. Ensure that your user account has the necessary permissions to access the site.​ + +== Installation ==​ + +No manual installation is necessary.​ + +== Screenshots ==​ + +* Initial release​ \ No newline at end of file From c8a783e7282125729f9620b00aa187e6d9b34539 Mon Sep 17 00:00:00 2001 From: John-Luke McCarthy Date: Thu, 17 Jul 2025 19:46:38 -0500 Subject: [PATCH 3/3] fixed file location --- pressable-basic-authentication.php | 341 +++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 pressable-basic-authentication.php diff --git a/pressable-basic-authentication.php b/pressable-basic-authentication.php new file mode 100644 index 0000000..22cee88 --- /dev/null +++ b/pressable-basic-authentication.php @@ -0,0 +1,341 @@ +is_ajax_request() ) { + return; + } + + // Skip if we're doing CRON. + if ( $this->is_cron_request() ) { + return; + } + + // Skip if we're in CLI mode. + if ( $this->is_cli_request() ) { + return; + } + + // Skip requests to excluded endpoints + if ($this->should_skip_auth()) { + return; + } + + // Handle logout request. + if ( isset( $_GET['basic-auth-logout'] ) ) { + $this->handle_basic_auth_logout(); + } + + // Redirect from wp-login.php when already authenticated via Basic Auth + $this->maybe_redirect_from_login_page(); + + // Force authentication. + $this->force_basic_authentication(); + } + + /** + * Force Basic Authentication + */ + private function force_basic_authentication() { + // Prevent caching of authentication requests. + $this->prevent_caching(); + + // Extract credentials from headers. + $this->extract_basic_auth_credentials(); + + // Allow Super Admins to bypass authentication. + if ( is_multisite() && is_super_admin() ) { + return; + } + + // Check if the user is already logged in. + if ( is_user_logged_in() ) { + return; + } + + // Check for Basic Authentication credentials. + $auth_user = isset( $_SERVER['PHP_AUTH_USER'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ) : null; + $auth_pass = isset( $_SERVER['PHP_AUTH_PW'] ) ? $_SERVER['PHP_AUTH_PW'] : null; + + if ( ! $auth_user || ! $auth_pass ) { + $this->log_failed_auth( 'Missing credentials' ); + $this->send_auth_headers(); + } + + // Validate credentials against WordPress users table. + $user = wp_authenticate( $auth_user, $auth_pass ); + if ( is_wp_error( $user ) ) { + $this->log_failed_auth( "Invalid credentials for user: $auth_user" ); + $this->send_auth_headers(); + } + + // Log the user in programmatically. + wp_set_current_user( $user->ID ); + wp_set_auth_cookie( $user->ID ); + } + + /** + * Logs failed authentication attempts to the error log. + * + * @param string $message The message to log. + */ + private function log_failed_auth( $message ) { + error_log( + sprintf( + '[%s] Basic Auth Failed: %s', + gmdate( 'Y-m-d H:i:s' ), + $message + ) + ); + } + + /** + * Check if the current request should skip authentication + * + * @return bool + */ + private function should_skip_auth() { + // List of endpoints to exclude from Basic Auth + $excluded_endpoints = array( + 'xmlrpc.php', + 'wp-json/jetpack', + 'wp-json/wp/v2', + 'wp-json/wp/v3' + ); + + // Get current request details + $request_uri = $_SERVER['REQUEST_URI'] ?? ''; + $script_name = $_SERVER['SCRIPT_NAME'] ?? ''; + + // Check if this is a direct xmlrpc.php request + if (basename($script_name) === 'xmlrpc.php') { + return true; + } + + // Check all excluded endpoints + foreach ($excluded_endpoints as $endpoint) { + if (strpos($request_uri, $endpoint) !== false) { + return true; + } + } + + // Check WordPress constants + if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) { + return true; + } + + if (defined('REST_REQUEST') && REST_REQUEST) { + return true; + } + + return false; + } + + /** + * Sends authentication headers. + */ + private function send_auth_headers() { + header( 'WWW-Authenticate: Basic realm="Restricted Area"' ); + header( 'HTTP/1.1 401 Unauthorized' ); + echo '

' . esc_html__( 'Authentication Required', 'pressable-basic-auth' ) . '

'; + exit; + } + + /** + * Use getallheaders() for Servers That Strip Authorization Headers + */ + private function extract_basic_auth_credentials() { + if ( ! empty( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_PW'] ) ) { + return; + } + + // Attempt to fetch credentials from Authorization header. + $auth_header = $this->get_authorization_header(); + + if ( ! $auth_header ) { + return; + } + + if ( 0 === stripos( $auth_header, 'basic ' ) ) { + $auth_encoded = substr( $auth_header, 6 ); + $auth_decoded = base64_decode( $auth_encoded ); + if ( $auth_decoded && strpos( $auth_decoded, ':' ) !== false ) { + list( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) = explode( ':', $auth_decoded, 2 ); + } + } + } + + /** + * Get the authorization header + * + * @return string|null The authorization header value or null + */ + private function get_authorization_header() { + if ( function_exists( 'getallheaders' ) ) { + $headers = getallheaders(); + + // Check for Authorization header (case-insensitive). + foreach ( $headers as $key => $value ) { + if ( strtolower( $key ) === 'authorization' ) { + return $value; + } + } + } + + // Try common alternative locations. + if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) { + return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); + } elseif ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) { + return wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ); + } + + return null; + } + + /** + * Handles Basic Auth logout by forcing a 401 response and then redirecting. + */ + private function handle_basic_auth_logout() { + wp_logout(); // Log out from WordPress. + + // Clear Basic Auth credentials by forcing a 401. + header( 'WWW-Authenticate: Basic realm="Restricted Area"' ); + header( 'HTTP/1.1 401 Unauthorized' ); + + // Output a JavaScript-based redirect after the 401 response. + echo ''; + + // End execution to prevent further processing. + exit; + } + + /** + * Modifies the default WordPress logout URL to trigger Basic Auth logout. + * + * @param string $logout_url The WordPress logout URL. + * @param string $redirect The redirect URL after logout. + * @return string Modified logout URL + */ + public function modify_logout_url( $logout_url, $redirect ) { + return add_query_arg( 'basic-auth-logout', '1', $logout_url ); + } + + /** + * Redirects from wp-login.php to home page when user is already authenticated via Basic Auth + */ + public function maybe_redirect_from_login_page() { + global $pagenow; + + // Check if we're on the login page and have Basic Auth credentials + if ( 'wp-login.php' === $pagenow && + ! empty( $_SERVER['PHP_AUTH_USER'] ) && + ! empty( $_SERVER['PHP_AUTH_PW'] ) && + ! isset( $_GET['action'] ) && + ! isset( $_GET['loggedout'] ) && + ! isset( $_POST['log'] ) ) { + + // Get appropriate home URL for either multisite or regular WordPress + if ( is_multisite() ) { + $redirect_url = network_home_url(); + + // If we can determine the current blog, go to its home instead + if ( isset( $_SERVER['HTTP_HOST'] ) ) { + $blog_details = get_blog_details( array( 'domain' => $_SERVER['HTTP_HOST'] ) ); + if ( $blog_details ) { + $redirect_url = get_home_url( $blog_details->blog_id ); + } + } + } else { + $redirect_url = home_url(); + } + + // Safe redirect + wp_safe_redirect( $redirect_url ); + exit; + } + } + + /** + * Prevent caching of authentication requests + */ + private function prevent_caching() { + header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); + header( 'Pragma: no-cache' ); + header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' ); + } + + /** + * Check if the current request is an AJAX request + * + * @return bool + */ + private function is_ajax_request() { + return ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || + ( ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && 'xmlhttprequest' === strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) ); + } + + /** + * Check if the current request is a cron request + * + * @return bool + */ + private function is_cron_request() { + return defined( 'DOING_CRON' ) && DOING_CRON; + } + + /** + * Check if the current request is a CLI request + * + * @return bool + */ + private function is_cli_request() { + return ( 'cli' === php_sapi_name() || ( defined( 'WP_CLI' ) && WP_CLI ) ); + } +} + +// Initialize the plugin. +new Pressable_Basic_Auth(); \ No newline at end of file