WordPress / application-passwords

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Basic Authorization Header Missing fix not working

shockthealien opened this issue · comments

Hi

I followed your instructions to fix the issue and added the code to the .htaccess file but I am still getting the error. Are there any other things that could be causing this?

Thanks

I've run into the same problem. With one of the latest WordPress changes (some time after August/September 2019) something must have changed inside WordPress' core. I assume there is something going on with the priorities of the filters/hooks for the REST subsystem.

Application Passwords relies on the constant REST_REQUEST (class.application-passwords.php::authenticate()) which is false, even if an HTTP request to one of my /wp-json endpoints has been triggered. For unknown reasons, the wp-includes/rest-api.php::rest_api_loaded() method is not been called, so that the REST_REQUEST constant doesn't get defined.

I've fixed this issue temporarily by adding a hook for the application_password_is_api_request filter:

add_filter('application_password_is_api_request', function($api_request) {
        if (empty($api_request)) {
                return strpos($_SERVER['REQUEST_URI'], '/wp-json/') !== false;

        }

        return $api_request;
}, 10, 1);

@schakko thanks for the reply. That sounds promising.

Does that hook go in the .htaccess file too? (sorry for my ignorance)

@shockthealien The hook goes into one of your wp-content/plugins files, e.g.

  • create a new file at wp-content/plugins/hook/index.php
  • paste the following into to index.php
<?php
/*
Plugin Name: My plugin
Description: My plugin
Version: 1.0.0
*/

add_filter('application_password_is_api_request', function($api_request) {
        if (empty($api_request)) {
                return strpos($_SERVER['REQUEST_URI'], '/wp-json/') !== false;

        }

        return $api_request;
}, 10, 1);

@schakko Thanks! I’ll give that a try

@schakko I added that to my sites and it doesn't seem to be working for me sadly

I followed your instructions to fix the issue and added the code to the .htaccess file but I am still getting the error. Are there any other things that could be causing this?

@shockthealien Could it be that the webserver is using something other than Apache -- maybe Nginx? That would have a different way to configure things.

I've run into the same problem. With one of the latest WordPress changes (some time after August/September 2019) something must have changed inside WordPress' core.

@schakko Could you please elaborate on what you mean? The plugin is using the determine_current_user filter to process the HTTP authentication headers for the REST requests. The application_password_is_api_request filter is used only for the "regular" login requests to disallow application passwords for regular logins.

It’s a possibility. The server is a shared one from 1and1. How can I find out for sure? If that’s the case is it a no-go?

@shockthealien Check the Site Health report under the "Info" tab at the top of the page. It should have all the server details.

Thanks I’ll have a look tomorrow and post the results

@schakko Could you please elaborate on what you mean? The plugin is using the determine_current_user filter to process the HTTP authentication headers for the REST requests. The application_password_is_api_request filter is used only for the "regular" login requests to disallow application passwords for regular logins.

Sure. When I am accessing my REST endpoint (/wp-json/...):

Thanks for that detailed explanation @schakko!

It looks like the first time the wp_get_current_user() is called is in line 609 of wp-includes/class-wp.php during WP->init() in wp-settings.php:

#0  Application_Passwords::authenticate(, admin, ) called at [/var/www/html/wp-content/plugins/application-passwords/class.application-passwords.php:264]
--
  | #1  Application_Passwords::rest_api_auth_handler() called at [/var/www/html/wp-includes/class-wp-hook.php:286]
  | #2  WP_Hook->apply_filters(, Array ([0] => )) called at [/var/www/html/wp-includes/plugin.php:208]
  | #3  apply_filters(determine_current_user, ) called at [/var/www/html/wp-includes/user.php:2698]
  | #4  _wp_get_current_user() called at [/var/www/html/wp-includes/pluggable.php:69]
  | #5  wp_get_current_user() called at [/var/www/html/wp-includes/class-wp.php:609]
  | #6  WP->init() called at [/var/www/html/wp-settings.php:512]
  | #7  require_once(/var/www/html/wp-settings.php) called at [/var/www/html/wp-config.php:96]
  | #8  require_once(/var/www/html/wp-config.php) called at [/var/www/html/wp-load.php:37]
  | #9  require_once(/var/www/html/wp-load.php) called at [/var/www/html/wp-blog-header.php:13]
  | #10 require(/var/www/html/wp-blog-header.php) called at [/var/www/html/index.php:17]

which is before the REST API has been initialised. It could be considered the expected behaviour since we only want this plugin to work for the actual API requests. Every authentication related check before the request has been parsed and routed to the REST controllers should be ignored.

The second time it gets called on the most basic WP install with just this plugin is during the REST API init:

#0  Application_Passwords::authenticate(, admin, ) called at [/var/www/html/wp-content/plugins/application-passwords/class.application-passwords.php:264]
#1  Application_Passwords::rest_api_auth_handler() called at [/var/www/html/wp-includes/class-wp-hook.php:286]
#2  WP_Hook->apply_filters(, Array ([0] => )) called at [/var/www/html/wp-includes/plugin.php:208]
#3  apply_filters(determine_current_user, ) called at [/var/www/html/wp-includes/user.php:2698]
#4  _wp_get_current_user() called at [/var/www/html/wp-includes/pluggable.php:69]
#5  wp_get_current_user() called at [/var/www/html/wp-includes/capabilities.php:640]
#6  current_user_can(unfiltered_html) called at [/var/www/html/wp-includes/functions.php:2872]
#7  get_allowed_mime_types() called at [/var/www/html/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:766]
#8  WP_REST_Attachments_Controller->get_media_types() called at [/var/www/html/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:684]
#9  WP_REST_Attachments_Controller->get_collection_params() called at [/var/www/html/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:68]
#10 WP_REST_Posts_Controller->register_routes() called at [/var/www/html/wp-includes/rest-api.php:205]
#11 create_initial_rest_routes(WP_REST_Server Object ([*namespaces] => Array ([oembed/1.0] => Array ([/oembed/1.0] => 1,[/oembed/1.0/embed] => 1,...
#12 WP_Hook->apply_filters(, Array ([0] => WP_REST_Server Object ([*namespaces] ...
#13 WP_Hook->do_action(Array ([0] => WP_REST_Server Object ([*namespaces] => ...
#14 do_action(rest_api_init, WP_REST_Server Object ([*namespaces] => Array ([oembed/1.0] => 
#15 rest_get_server() called at [/var/www/html/wp-includes/rest-api.php:302]
#16 rest_api_loaded(WP Object ([public_query_vars] => Array ([0] => m,[1] => p,
#17 WP_Hook->apply_filters(, Array ([0] => WP Object ([public_query_vars] => Array 
#18 WP_Hook->do_action(Array ([0] => WP Object ([public_query_vars] => Array ([0] => 
#19 do_action_ref_array(parse_request, Array ([0] => WP Object ([public_query_vars] => 
#20 WP->parse_request() called at [/var/www/html/wp-includes/class-wp.php:737]
#21 WP->main() called at [/var/www/html/wp-includes/functions.php:1105]
#22 wp() called at [/var/www/html/wp-blog-header.php:16]
#23 require(/var/www/html/wp-blog-header.php) called at [/var/www/html/index.php:17]

So any authentication checks for the actual REST requests should work as expected.

What do you think?

@kasparsd heres the server info. It seems to be Apache
Screenshot 2020-01-09 at 10 03 58

@shockthealien Could you please post a detailed description of your issue -- what API endpoint are you trying to access, what is the exact request URL and the request headers?

I followed your instructions to fix the issue and added the code to the .htaccess file but I am still getting the error. Are there any other things that could be causing this?

When are you getting this error? Where is it being returned? How can I replicate it with a basic install of WP and this plugin?

@kasparsd I'll try but I am not hugely technical.

Basically I am trying to use the Distributor plugin from 10up (https://distributorplugin.com), and they require this plugin to be installed to work. (or to create a WP application)

They are trying to reach the http://yourdomain.com/wp-json

I tried a normal installation on the sites I was going working on and got the error, so I did a clean install of WordPress, updated to latest version and installed AP first. But get the error straight away

Screenshot 2020-01-09 at 10 21 56

I followed the instructions to try to fix it adding the code to the .htaccess file but didnt solve it.
then I did the hook above and still didnt solve it.

Is there anything else you need? Im not sure how to figure out API endpoint or headers, sorry

Thanks for the explanation @shockthealien!

The warning is set if the web server fails this check HTTP Basic Auth check:

$.ajax( {
url: appPass.root + appPass.namespace + '/test-basic-authorization-header',
method: 'POST',
beforeSend: function( xhr ) {
xhr.setRequestHeader( 'Authorization', 'Basic ' + btoa( testBasicAuthUser + ':' + testBasicAuthPassword ) );
},
error: function( jqXHR ) {
if ( 404 === jqXHR.status ) {
$newAppPassForm.before( tmplNotice( {
type: 'error',
message: appPass.text.no_credentials
} ) );
}
}
} ).done( function( response ) {
if ( response.PHP_AUTH_USER === testBasicAuthUser && response.PHP_AUTH_PW === testBasicAuthPassword ) {
// Save the success in SessionStorage or the like, so we don't do it on every page load?
} else {
$newAppPassForm.before( tmplNotice( {
type: 'error',
message: appPass.text.no_credentials
} ) );
}
} );

You should reach out to your web hosting support about this. PHP needs access to $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] during HTTP requests with Basic Auth headers.

public static function rest_test_basic_authorization_header() {
$response = array();
if ( isset( $_SERVER['PHP_AUTH_USER'] ) ) {
$response['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER'];
}
if ( isset( $_SERVER['PHP_AUTH_PW'] ) ) {
$response['PHP_AUTH_PW'] = $_SERVER['PHP_AUTH_PW'];
}
if ( empty( $response ) ) {
return new WP_Error( 'no-credentials', __( 'No HTTP Basic Authorization credentials were found submitted with this request.' ), array( 'status' => 404 ) );
}
return $response;
}

Note that we try to support different server configurations but it appears that it also isn't working in this case:

/**
* Some servers running in CGI or FastCGI mode don't pass the Authorization
* header on to WordPress. If it's been rewritten to the `REMOTE_USER` header,
* fill in the proper $_SERVER variables instead.
*/
public static function fallback_populate_username_password() {
// If we don't have anything to pull from, return early.
if ( ! isset( $_SERVER['REMOTE_USER'] ) && ! isset( $_SERVER['REDIRECT_REMOTE_USER'] ) ) {
return;
}
// If either PHP_AUTH key is already set, do nothing.
if ( isset( $_SERVER['PHP_AUTH_USER'] ) || isset( $_SERVER['PHP_AUTH_PW'] ) ) {
return;
}
// From our prior conditional, one of these must be set.
$header = isset( $_SERVER['REMOTE_USER'] ) ? $_SERVER['REMOTE_USER'] : $_SERVER['REDIRECT_REMOTE_USER'];
// Test to make sure the pattern matches expected.
if ( ! preg_match( '%^Basic [a-z\d/+]*={0,2}$%i', $header ) ) {
return;
}
// Removing `Basic ` the token would start six characters in.
$token = substr( $header, 6 );
$userpass = base64_decode( $token );
list( $user, $pass ) = explode( ':', $userpass );
// Now shove them in the proper keys where we're expecting later on.
$_SERVER['PHP_AUTH_USER'] = $user;
$_SERVER['PHP_AUTH_PW'] = $pass;
return array( $user, $pass );
}

Thanks. I will try to get them to resolve that. Tho im not confident they will. Fingers crossed

@kasparsd I am out of ideas. I can confirm that with a basic WordPress installation (WordPress 5.2.6, Application Passwords 0.1.1), the required headers are filled but the following code fails:

add_action('rest_api_init', function () {
	register_rest_route('download-monitor-release-version/v1', '/downloads/(?P<id>\d+)/release', array(
		'methods' => 'POST',
		'callback' => 'dlm_release_version_rest_callback',
		'permission_callback' => function () {
			$can_manage = current_user_can('read');
			return $can_manage;
		},
	));
}

Permissions for permission_callback are valid and did work until Q4/2019.

@schakko yeah as I suspected the hosting company didn't know how to fix that, so I guess I am screwed and with nowhere to go

I'm getting the same error message as well even after following the wiki and modifying the .htaccess file. I'm using bitnami on ec2

I tried printing out the server variables you posted @kasparsd

<?php
echo $_SERVER['PHP_AUTH_USER'];
echo $_SERVER['PHP_AUTH_PW'];
?>

but i don't think they're set:
PHP Notice: Undefined index: PHP_AUTH_USER in /home/bitnami/test.php on line 2
PHP Notice: Undefined index: PHP_AUTH_PW in /home/bitnami/test.php on line 3

Any idea where to go from here?