Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

All users are defined as Visitor in REST API #11

Open
artemiusgreat opened this issue Jan 13, 2020 · 7 comments
Open

All users are defined as Visitor in REST API #11

artemiusgreat opened this issue Jan 13, 2020 · 7 comments

Comments

@artemiusgreat
Copy link

@artemiusgreat artemiusgreat commented Jan 13, 2020

WordPress 5.3.2
Disabled all plugins except AAM.

To reproduce the issue.

  1. Go to WP Admin Panel => AAM plugin page
  2. Select "Manage Rules for Visitors"
  3. Pick any REST API URL, e.g. /wp/v2/posts, and check the box "Deny" for it
  4. Select "Manage Rules for Administrators", make sure that checkbox "Deny" is unchecked

Now

  1. Open some page as unauthenticated user and try to get /wp/v2/posts
  2. It shows access denied as expected for unauthenticated visitors

Then

  1. Try to add JWT token created in admin panel to any valid place - query string, post body, authentication header, or authorization header and try to get /wp/v2/posts
  2. Still "Access Denied" even though I tried to use JST token in various places

The issue #1 why I get "Access Denied" is because this method can identify user only in admin panel. When REST requests goes through it, the method below is called multiple times, and on the last call WP_User is always identified as "Unauthenticated Visitor".

/**
 * Change current user
 *
 * This method is triggered if some process updates current user
 *
 * @return AAM_Core_Subject
 *
 * @access public
 * @version 6.0.0
 */
public function initializeUser()
{
    global $current_user;

    // Important! Do not use WP core function to avoid loop
    $id = (is_a($current_user, 'WP_User') ? $current_user->ID : null);

// is_a() method works only in admin panel, but not in REST API
print '<pre>';
var_dump(is_a($current_user, 'WP_User'));
print_r($current_user);

   // Change current user
    if ($id) {
        $user = (new AAM_Core_Subject_User($id))->initialize();
    } else {
        $user = new AAM_Core_Subject_Visitor();
    }

    $this->setUser($user);

    return $user;
}

@artemiusgreat

This comment has been minimized.

Copy link
Author

@artemiusgreat artemiusgreat commented Jan 13, 2020

Output from var_dump() above. In some reason this method is called multiple times.

false
NULL

true
WP_User { "ID" => "1" }

true 
WP_User { "ID" => "0" }

{"code":"rest_access_denied","message":"Access Denied","data":{"status":401}}
@artemiusgreat

This comment has been minimized.

Copy link
Author

@artemiusgreat artemiusgreat commented Jan 13, 2020

Then, I install this plugin, add required settings and add "Authorization" header to /wp/v2/posts request.
https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/

Now, I'm getting correct output

false
NULL

true
WP_User { "ID" => "1" }

{"posts": ... }
@artemiusgreat

This comment has been minimized.

Copy link
Author

@artemiusgreat artemiusgreat commented Jan 13, 2020

Issue #2 is that these 2 plugins, JWT Authentication and AAM work good together, but I cannot use JWT token generated by AAM plugin in admin panel, because

JWT Authentication plugin expects base 64 token in the format
{ data: { user: { id: 1 }}}

AAM generates token in this format
{ userId: 1 }

@artemiusgreat

This comment has been minimized.

Copy link
Author

@artemiusgreat artemiusgreat commented Jan 13, 2020

I still have no idea why method initializeUser() is called 3 times with different user IDs within the same API call, but maybe this stack trace will shed some light

public function initializeUser()
{
    global $current_user;

    // Important! Do not use WP core function to avoid loop
    $id = (is_a($current_user, 'WP_User') ? $current_user->ID : null);

    // Change current user
    if ($id) {
        $user = (new AAM_Core_Subject_User($id))->initialize();
    } else {
        $user = new AAM_Core_Subject_Visitor();
    }

    $this->setUser($user);
$e = new Exception();
print '<pre>';
print_r($e->getTraceAsString());
print "\n";
var_dump($id);
//die();
    return $user;
}

As you can see below, at some point WP calls wp_set_current_user(0)...

<pre>#0 D:\Code\PhpServer\htdocs\com.wp\cms\wp-content\plugins\advanced-access-manager\aam.php(59): AAM->initializeUser()
#1 D:\Code\PhpServer\htdocs\com.wp\cms\wp-content\plugins\advanced-access-manager\aam.php(213): AAM->__construct()
#2 D:\Code\PhpServer\htdocs\com.wp\cms\wp-content\plugins\advanced-access-manager\aam.php(184): AAM::getInstance()
#3 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(288): AAM::onPluginsLoaded('')
#4 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(312): WP_Hook->apply_filters('', Array)
#5 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\plugin.php(478): WP_Hook->do_action(Array)
#6 D:\Code\PhpServer\htdocs\com.wp\cms\wp-settings.php(392): do_action('plugins_loaded')
#7 D:\Code\PhpServer\htdocs\com.wp\cms\wp-config.php(92): require_once('D:\\Code\\PhpServ...')
#8 D:\Code\PhpServer\htdocs\com.wp\cms\wp-load.php(37): require_once('D:\\Code\\PhpServ...')
#9 D:\Code\PhpServer\htdocs\com.wp\cms\wp-blog-header.php(13): require_once('D:\\Code\\PhpServ...')
#10 D:\Code\PhpServer\htdocs\com.wp\cms\index.php(17): require('D:\\Code\\PhpServ...')
#11 {main}
NULL
<pre>#0 D:\Code\PhpServer\htdocs\com.wp\cms\wp-content\plugins\advanced-access-manager\aam.php(63): AAM->initializeUser()
#1 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(288): AAM->{closure}('')
#2 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(312): WP_Hook->apply_filters(NULL, Array)
#3 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\plugin.php(478): WP_Hook->do_action(Array)
#4 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\pluggable.php(47): do_action('set_current_use...')
#5 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\user.php(2764): wp_set_current_user(1)
#6 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\pluggable.php(69): _wp_get_current_user()
#7 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\pluggable.php(1002): wp_get_current_user()
#8 D:\Code\PhpServer\htdocs\com.wp\cms\wp-content\plugins\advanced-access-manager\aam.php(219): is_user_logged_in()
#9 D:\Code\PhpServer\htdocs\com.wp\cms\wp-content\plugins\advanced-access-manager\aam.php(184): AAM::getInstance()
#10 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(288): AAM::onPluginsLoaded('')
#11 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(312): WP_Hook->apply_filters('', Array)
#12 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\plugin.php(478): WP_Hook->do_action(Array)
#13 D:\Code\PhpServer\htdocs\com.wp\cms\wp-settings.php(392): do_action('plugins_loaded')
#14 D:\Code\PhpServer\htdocs\com.wp\cms\wp-config.php(92): require_once('D:\\Code\\PhpServ...')
#15 D:\Code\PhpServer\htdocs\com.wp\cms\wp-load.php(37): require_once('D:\\Code\\PhpServ...')
#16 D:\Code\PhpServer\htdocs\com.wp\cms\wp-blog-header.php(13): require_once('D:\\Code\\PhpServ...')
#17 D:\Code\PhpServer\htdocs\com.wp\cms\index.php(17): require('D:\\Code\\PhpServ...')
#18 {main}
int(1)
<pre>#0 D:\Code\PhpServer\htdocs\com.wp\cms\wp-content\plugins\advanced-access-manager\aam.php(63): AAM->initializeUser()
#1 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(288): AAM->{closure}('')
#2 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(312): WP_Hook->apply_filters(NULL, Array)
#3 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\plugin.php(478): WP_Hook->do_action(Array)
#4 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\pluggable.php(47): do_action('set_current_use...')
#5 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\rest-api.php(906): wp_set_current_user(0)
#6 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(288): rest_cookie_check_errors(NULL)
#7 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\plugin.php(206): WP_Hook->apply_filters(NULL, Array)
#8 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\rest-api\class-wp-rest-server.php(135): apply_filters('rest_authentica...', NULL)
#9 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\rest-api\class-wp-rest-server.php(326): WP_REST_Server->check_authentication()
#10 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\rest-api.php(305): WP_REST_Server->serve_request('/wp/v2/posts')
#11 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(288): rest_api_loaded(Object(WP))
#12 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp-hook.php(312): WP_Hook->apply_filters(NULL, Array)
#13 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\plugin.php(544): WP_Hook->do_action(Array)
#14 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp.php(387): do_action_ref_array('parse_request', Array)
#15 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\class-wp.php(729): WP->parse_request('')
#16 D:\Code\PhpServer\htdocs\com.wp\cms\wp-includes\functions.php(1255): WP->main('')
#17 D:\Code\PhpServer\htdocs\com.wp\cms\wp-blog-header.php(16): wp()
#18 D:\Code\PhpServer\htdocs\com.wp\cms\index.php(17): require('D:\\Code\\PhpServ...')
#19 {main}
int(0)
{"code":"rest_access_denied","message":"Access Denied","data":{"status":401}}
@artemiusgreat

This comment has been minimized.

Copy link
Author

@artemiusgreat artemiusgreat commented Jan 14, 2020

Patching method AAM::initializeUser() this way could fix the issue, but the hack is quite dirty.

public function initializeUser()
{
    global $current_user;

    $id = get_current_user_id();

    if (empty($id)) {
      $token = @$_REQUEST['aam-jwt'] ?: $_COOKIE['aam_jwt_token'] ?: $_SERVER['HTTP_AUTHORIZATION'];
      $id = @json_decode(base64_decode(explode('.', $token)[1]))->userId;
    }

    // Change current user
    if ($id) {
        $user = (new AAM_Core_Subject_User($id))->initialize();
    } else {
        $user = new AAM_Core_Subject_Visitor();
    }

    $this->setUser($user);

    return $user;
}
@aamplugin

This comment has been minimized.

Copy link
Owner

@aamplugin aamplugin commented Jan 15, 2020

Thank you for the details. I believe your problems are "mechanical". First and foremost, AAM does not use Authorization header to pass the JWT token but rather Authentication. The reasoning behind it are explained in this article https://aamplugin.com/article/ultimate-guide-to-wordpress-jwt-authentication.

Now, if you pass JWT token as query param ($_GET), AAM automatically assumes that you are doing passwordless authentication and issues auth cookies. That is why it is most likely auth cookies and JWT token is passed in a subsequent call to the API and that creates a problem because you are trying to authenticate a user in two different ways (cookies and JWT).

I've been trying to replicate issues per your description without success, so let's continue the conversation.

@artemiusgreat

This comment has been minimized.

Copy link
Author

@artemiusgreat artemiusgreat commented Jan 15, 2020

Thank you for the reply. I've read the article and have seen notice about "authentication" instead of "authorization". Tried both, separately and together, only when I pass "authorization" and set it up for another plugin, my administrator role finally can get access to restricted route. This is why I mentioned "authorization".

I'm using ARC extension to send requests from Chrome.
https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo
Direct execution of request in browser gives the same results.

Encoded information

{
  "iat": 1578880092,
  "iss": "http://com.wp",
  "exp": 1580950857,
  "jti": "ae9e5be1-427b-4cdc-aaa0-f31be87aac1d",
  "userId": 1,
  "revocable": true,
  "refreshable": true
}

1. Example of request with GET param

http://com.wp/wp-json/wp/v2/posts?aam-jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1Nzg4ODAwOTIsImlzcyI6Imh0dHA6XC9cL2NvbS53cCIsImV4cCI6MTU4MDk1MDg1NywianRpIjoiYWU5ZTViZTEtNDI3Yi00Y2RjLWFhYTAtZjMxYmU4N2FhYzFkIiwidXNlcklkIjoxLCJyZXZvY2FibGUiOnRydWUsInJlZnJlc2hhYmxlIjp0cnVlfQ.7sbRgP1AklkA4aHKs7SH6egzdjtLRczhn6fx9ses5g8

2. Example with headers and cookies

Number "1" added to disable specific header. Works only when I use "authorization" token below generated by another plugin.

accept: text/plain
content-type: application/json
cookie1: aam_jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1Nzg4ODAwOTIsImlzcyI6Imh0dHA6XC9cL2NvbS53cCIsImV4cCI6MTU4MDk1MDg1NywianRpIjoiYWU5ZTViZTEtNDI3Yi00Y2RjLWFhYTAtZjMxYmU4N2FhYzFkIiwidXNlcklkIjoxLCJyZXZvY2FibGUiOnRydWUsInJlZnJlc2hhYmxlIjp0cnVlfQ.7sbRgP1AklkA4aHKs7SH6egzdjtLRczhn6fx9ses5g8
authorization1: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9jb20ud3AiLCJpYXQiOjE1Nzg4OTAxMDksIm5iZiI6MTU3ODg5MDEwOSwiZXhwIjoxNTc5NDk0OTA5LCJkYXRhIjp7InVzZXIiOnsiaWQiOiIxIn19fQ._4DeeWo4C9TthtzO4SdEKNJLuGV49HJ5spCGinmibS8
authentication1: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1Nzg4ODAwOTIsImlzcyI6Imh0dHA6XC9cL2NvbS53cCIsImV4cCI6MTU4MDk1MDg1NywianRpIjoiYWU5ZTViZTEtNDI3Yi00Y2RjLWFhYTAtZjMxYmU4N2FhYzFkIiwidXNlcklkIjoxLCJyZXZvY2FibGUiOnRydWUsInJlZnJlc2hhYmxlIjp0cnVlfQ.7sbRgP1AklkA4aHKs7SH6egzdjtLRczhn6fx9ses5g8

I tried to use only one each of them, e.g. only GET or only Authentication header. Without Authorization header API route stays "denied" even for administrators with valid JWT tokens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.