From 2df94804d419c32398cafad6875396a98328ac57 Mon Sep 17 00:00:00 2001
From: TimothyBlynJacobs
Date: Tue, 27 Oct 2020 18:32:07 +0000
Subject: [PATCH] Site Health, App Passwords: Test if the Authorization header
is populated correctly.
App Passwords rely on the Authorization header to transport the Basic Auth credentials. For Apache web servers, WordPress automatically includes a RewriteRule to populate the value for servers running in CGI or FastCGI that wouldn't ordinarily populate the value.
This tests if the header is being filled with the expected values. For Apache users, we direct the user to visit the Permalinks settings to flush their permalinks. For all other users, we direct them to a help document on developer.wordpress.org.
Props Clorith, marybaum, TimothyBlynJacobs.
Fixes #51638.
Built from https://develop.svn.wordpress.org/trunk@49334
git-svn-id: http://core.svn.wordpress.org/trunk@49095 1a063a9b-81f0-0310-95a4-ce76da25c4cd
---
wp-admin/includes/class-wp-site-health.php | 70 +++++++++++++++++++
wp-admin/js/site-health.js | 3 +-
wp-admin/js/site-health.min.js | 2 +-
.../class-wp-rest-site-health-controller.php | 30 ++++++++
wp-includes/version.php | 2 +-
5 files changed, 104 insertions(+), 3 deletions(-)
diff --git a/wp-admin/includes/class-wp-site-health.php b/wp-admin/includes/class-wp-site-health.php
index 2e556f40a8..19491d2eed 100644
--- a/wp-admin/includes/class-wp-site-health.php
+++ b/wp-admin/includes/class-wp-site-health.php
@@ -135,6 +135,7 @@ class WP_Site_Health {
'test' => $test['test'],
'has_rest' => ( isset( $test['has_rest'] ) ? $test['has_rest'] : false ),
'completed' => false,
+ 'headers' => isset( $test['headers'] ) ? $test['headers'] : array(),
);
}
}
@@ -2078,6 +2079,62 @@ class WP_Site_Health {
return $result;
}
+ /**
+ * Tests if the Authorization header has the expected values.
+ *
+ * @since 5.6.0
+ *
+ * @return array
+ */
+ public function get_test_authorization_header() {
+ $result = array(
+ 'label' => __( 'The Authorization header is working as expected.' ),
+ 'status' => 'good',
+ 'badge' => array(
+ 'label' => __( 'Security' ),
+ 'color' => 'blue',
+ ),
+ 'description' => sprintf(
+ '%s
',
+ __( 'The Authorization header comes from the third-party applications you approve. Without it, those apps cannot connect to your site.' )
+ ),
+ 'actions' => '',
+ 'test' => 'authorization_header',
+ );
+
+ if ( ! isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) {
+ $result['label'] = __( 'The authorization header is missing.' );
+ } elseif ( 'user' !== $_SERVER['PHP_AUTH_USER'] || 'pwd' !== $_SERVER['PHP_AUTH_PW'] ) {
+ $result['label'] = __( 'The authorization header is invalid.' );
+ } else {
+ return $result;
+ }
+
+ $result['status'] = 'recommended';
+
+ if ( ! function_exists( 'got_mod_rewrite' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/misc.php';
+ }
+
+ if ( got_mod_rewrite() ) {
+ $result['actions'] .= sprintf(
+ '%s
',
+ esc_url( admin_url( 'options-permalink.php' ) ),
+ __( 'Flush permalinks' )
+ );
+ } else {
+ $result['actions'] .= sprintf(
+ '%s %s
',
+ 'https://developer.wordpress.org/rest-api/frequently-asked-questions/#why-is-authentication-not-working',
+ __( 'Learn how to configure the Authorization header.' ),
+ /* translators: Accessibility text. */
+ __( '(opens in a new tab)' )
+ );
+ }
+
+ return $result;
+ }
+
/**
* Return a set of tests that belong to the site status page.
*
@@ -2177,6 +2234,13 @@ class WP_Site_Health {
'has_rest' => true,
'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_loopback_requests' ),
),
+ 'authorization_header' => array(
+ 'label' => __( 'Authorization header' ),
+ 'test' => rest_url( 'wp-site-health/v1/tests/authorization-header' ),
+ 'has_rest' => true,
+ 'headers' => array( 'Authorization' => 'Basic ' . base64_encode( 'user:pwd' ) ),
+ 'skip_cron' => true,
+ ),
),
);
@@ -2203,6 +2267,7 @@ class WP_Site_Health {
*
* @since 5.2.0
* @since 5.6.0 Added the `async_direct_test` array key.
+ * Added the `skip_cron` array key.
*
* @param array $test_type {
* An associative array, where the `$test_type` is either `direct` or
@@ -2217,6 +2282,7 @@ class WP_Site_Health {
* @type mixed $test A callable to perform a direct test, or a string AJAX action
* to be called to perform an async test.
* @type boolean $has_rest Optional. Denote if `$test` has a REST API endpoint.
+ * @type boolean $skip_cron Whether to skip this test when running as cron.
* @type callable $async_direct_test A manner of directly calling the test marked as asynchronous,
* as the scheduled event can not authenticate, and endpoints
* may require authentication.
@@ -2557,6 +2623,10 @@ class WP_Site_Health {
}
foreach ( $tests['async'] as $test ) {
+ if ( ! empty( $test['skip_cron'] ) ) {
+ continue;
+ }
+
// Local endpoints may require authentication, so asynchronous tests can pass a direct test runner as well.
if ( ! empty( $test['async_direct_test'] ) && is_callable( $test['async_direct_test'] ) ) {
// This test is callable, do so and continue to the next asynchronous check.
diff --git a/wp-admin/js/site-health.js b/wp-admin/js/site-health.js
index 54497464cd..3314f41e1e 100644
--- a/wp-admin/js/site-health.js
+++ b/wp-admin/js/site-health.js
@@ -212,7 +212,8 @@ jQuery( document ).ready( function( $ ) {
if ( 'undefined' !== typeof( this.has_rest ) && this.has_rest ) {
wp.apiRequest( {
- url: this.test
+ url: this.test,
+ headers: this.headers
} )
.done( function( response ) {
/** This filter is documented in wp-admin/includes/class-wp-site-health.php */
diff --git a/wp-admin/js/site-health.min.js b/wp-admin/js/site-health.min.js
index fecd022138..1333d7a01f 100644
--- a/wp-admin/js/site-health.min.js
+++ b/wp-admin/js/site-health.min.js
@@ -1,2 +1,2 @@
/*! This file is auto-generated */
-jQuery(document).ready(function(c){var a,t,s,h=wp.i18n.__,n=wp.i18n._n,o=wp.i18n.sprintf,i=new ClipboardJS(".site-health-copy-buttons .copy-button"),d=c(".health-check-body.health-check-debug-tab").length,l=c("#health-check-accordion-block-wp-paths-sizes");function r(e){var t,s,a=wp.template("health-check-issue"),i=c("#health-check-issues-"+e.status);SiteHealth.site_status.issues[e.status]++,s=SiteHealth.site_status.issues[e.status],void 0===e.test&&(e.test=e.status+s),"critical"===e.status?t=o(n("%s critical issue","%s critical issues",s),''+s+""):"recommended"===e.status?t=o(n("%s recommended improvement","%s recommended improvements",s),''+s+""):"good"===e.status&&(t=o(n("%s item with no issues detected","%s items with no issues detected",s),''+s+"")),t&&c(".site-health-issue-count-title",i).html(t),c(".issues","#health-check-issues-"+e.status).append(a(e))}function u(){var e,t,s=c(".site-health-progress"),a=s.closest(".site-health-progress-wrapper"),i=c(".site-health-progress-label",a),n=c(".site-health-progress svg #bar"),o=parseInt(SiteHealth.site_status.issues.good,0)+parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),l=.5*parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),r=100-Math.ceil(l/o*100);0!==o?(a.removeClass("loading"),e=n.attr("r"),r<0&&(r=0),100"+e+"
"+t+"
",actions:""},r(wp.hooks.applyFilters("site_status_test_result",s))}i.on("success",function(e){var t=c(e.trigger),s=c(".success",t.closest("div"));e.clearSelection(),t.focus(),clearTimeout(a),s.removeClass("hidden"),a=setTimeout(function(){s.addClass("hidden"),i.clipboardAction.fakeElem&&i.clipboardAction.removeFake&&i.clipboardAction.removeFake()},3e3),wp.a11y.speak(h("Site information has been copied to your clipboard."))}),c(".health-check-accordion").on("click",".health-check-accordion-trigger",function(){"true"===c(this).attr("aria-expanded")?(c(this).attr("aria-expanded","false"),c("#"+c(this).attr("aria-controls")).attr("hidden",!0)):(c(this).attr("aria-expanded","true"),c("#"+c(this).attr("aria-controls")).attr("hidden",!1))}),c(".site-health-view-passed").on("click",function(){var e=c("#health-check-issues-good");e.toggleClass("hidden"),c(this).attr("aria-expanded",!e.hasClass("hidden"))}),"undefined"==typeof SiteHealth||d||(0===SiteHealth.site_status.direct.length&&0===SiteHealth.site_status.async.length?u():SiteHealth.site_status.issues={good:0,recommended:0,critical:0},0'+s+""):"recommended"===e.status?t=o(n("%s recommended improvement","%s recommended improvements",s),''+s+""):"good"===e.status&&(t=o(n("%s item with no issues detected","%s items with no issues detected",s),''+s+"")),t&&h(".site-health-issue-count-title",i).html(t),h(".issues","#health-check-issues-"+e.status).append(a(e))}function u(){var e,t,s=h(".site-health-progress"),a=s.closest(".site-health-progress-wrapper"),i=h(".site-health-progress-label",a),n=h(".site-health-progress svg #bar"),o=parseInt(SiteHealth.site_status.issues.good,0)+parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),l=.5*parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),r=100-Math.ceil(l/o*100);0!==o?(a.removeClass("loading"),e=n.attr("r"),r<0&&(r=0),100"+e+""+t+"
",actions:""},r(wp.hooks.applyFilters("site_status_test_result",s))}i.on("success",function(e){var t=h(e.trigger),s=h(".success",t.closest("div"));e.clearSelection(),t.focus(),clearTimeout(a),s.removeClass("hidden"),a=setTimeout(function(){s.addClass("hidden"),i.clipboardAction.fakeElem&&i.clipboardAction.removeFake&&i.clipboardAction.removeFake()},3e3),wp.a11y.speak(c("Site information has been copied to your clipboard."))}),h(".health-check-accordion").on("click",".health-check-accordion-trigger",function(){"true"===h(this).attr("aria-expanded")?(h(this).attr("aria-expanded","false"),h("#"+h(this).attr("aria-controls")).attr("hidden",!0)):(h(this).attr("aria-expanded","true"),h("#"+h(this).attr("aria-controls")).attr("hidden",!1))}),h(".site-health-view-passed").on("click",function(){var e=h("#health-check-issues-good");e.toggleClass("hidden"),h(this).attr("aria-expanded",!e.hasClass("hidden"))}),"undefined"==typeof SiteHealth||d||(0===SiteHealth.site_status.direct.length&&0===SiteHealth.site_status.async.length?u():SiteHealth.site_status.issues={good:0,recommended:0,critical:0},0namespace,
+ sprintf(
+ '/%s/%s',
+ $this->rest_base,
+ 'authorization-header'
+ ),
+ array(
+ array(
+ 'methods' => 'GET',
+ 'callback' => array( $this, 'test_authorization_header' ),
+ 'permission_callback' => function () {
+ return $this->validate_request_permission( 'authorization_header' );
+ },
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
register_rest_route(
$this->namespace,
sprintf(
@@ -177,6 +196,17 @@ class WP_REST_Site_Health_Controller extends WP_REST_Controller {
return $this->site_health->get_test_loopback_requests();
}
+ /**
+ * Checks that the authorization header is valid.
+ *
+ * @since 5.6.0
+ *
+ * @return array
+ */
+ public function test_authorization_header() {
+ return $this->site_health->get_test_authorization_header();
+ }
+
/**
* Gets the current directory sizes for this install.
*
diff --git a/wp-includes/version.php b/wp-includes/version.php
index e0b1af5164..51ea30e4a7 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -13,7 +13,7 @@
*
* @global string $wp_version
*/
-$wp_version = '5.6-beta1-49333';
+$wp_version = '5.6-beta1-49334';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.