Make WordPress Core

Opened 8 years ago

Closed 8 years ago

#39839 closed defect (bug) (invalid)

Permissions processed differently between REST API and UI access causing 403 error

Reported by: reldev's profile reldev Owned by:
Milestone: Priority: normal
Severity: normal Version: 4.7.2
Component: REST API Keywords:
Focuses: Cc:

Description

I’m testing the REST API and have discovered a problem with how the REST API processes permissions. I am able to create a post or page successfully as well as update a post or page successfully, however, attempting to retrieve a draft post or page I had previously created fails with a 403 status from line 902 in class-wp-rest-server.php.

For reference, I’m testing urls of the form:

http://<domain>/wp-json/wp/v2/posts/<id>

and

http://<domain>/wp-json/wp/v2/posts/<id>?context=edit

The reason for the failure is because the function get_item_permissions_check in class-wp-rest-posts-controller.php is returning false to class-wp-rest-server.php at the invocation on line 897. if $request[‘context’] is "edit", get_item_permissions_check makes a call to check_update_permission on line 395 (which always succeeds). It then makes a call to check_read_permission on line 412 and returns the return value of that function call to class-wp-rest-server.php. This return value is always false, causing class-wp-rest-server.php to fail the request with 403 on line 902.

check_read_permission makes a call to current_user_can on line 1274 to check for the read_post capability. This call eventually invokes the has_cap function in class-wp-user.php on line 715. “read_post” is mapped to a meta capability of “read” on line 723. In the subsequent check for “read” in all the user’s capabilities ($capabilities) on line 750, “read” is not found, therefore the function returns false to check_read_permission, which in turn, returns false to get_item_permissions and back to class-wp-rest-server.php as well.

The problem appears not to be in the evaluation of the permission by check_read_permission and subsequently called routines, but in the handling of the returned value by class-wp-rest-server.php. I verified this by analyzing these permission checks when viewing or editing the same draft post from the UI.

In this case, I logged in as the same user I’m testing with from my REST API test program and clicked on Edit for the post I’ve been testing with which evaluates to a a url of the form:

http://<domain>/wp-admin/post.php?post=<id>&action=edit

In this case, there are a couple of calls to current_user_can for “read_post” from various locations in the code. admin-bar.php on line 593 makes a call to current_user_can for “read_post” and the evaluation of the permission from this point forward is identical to the REST-based request. “read_post” is mapped to a meta capability of “read” which is still not found in the full list of the user’s capabilities (wp-user.php line 750). The difference between the REST-based request and the UI request is how this false is ultimately handled. In the REST case, this false causes the request to fail with a 403. In this UI case, this false is returned to admin-bar.php on line 593 but processing continues. Likewise wp-admin->includes->post.php on line 1309 calls current_user_can for “read_post” and the evaluation of the permission occurs the same. In neither case, however, does the fact the check for the “read_post” capability returns false impact processing.

In another instance, I tested from the UI with a url of the form:

http://<domain>/?p=<id>

Using this approach to preview the draft post (while logged in as the same user), the “read_post” capability is never checked, only “edit_post” and “read” in various places. In each case, “read” meta maps to “read” which is again never found in the user’s capabilities, however, processing continues and the page displays as expected.

For reference, in either the UI or API case, the user’s full set of capabilities is identical as determined in has_cap on line 743 in class-wp-user.php:

    [switch_themes] => 1
    [edit_themes] => 1
    [activate_plugins] => 1
    [edit_plugins] => 1
    [edit_users] => 1
    [edit_files] => 1
    [manage_options] => 1
    [moderate_comments] => 1
    [manage_categories] => 1
    [manage_links] => 1
    [upload_files] => 1
    [import] => 1
    [unfiltered_html] => 1
    [edit_posts] => 1
    [edit_others_posts] => 1
    [edit_published_posts] => 1
    [publish_posts] => 1
    [edit_pages] => 1
    [edit_others_pages] => 1
    [edit_published_pages] => 1
    [publish_pages] => 1
    [delete_pages] => 1
    [delete_others_pages] => 1
    [delete_published_pages] => 1
    [delete_posts] => 1
    [delete_others_posts] => 1
    [delete_published_posts] => 1
    [delete_private_posts] => 1
    [edit_private_posts] => 1
    [read_private_posts] => 1
    [delete_private_pages] => 1
    [edit_private_pages] => 1
    [read_private_pages] => 1
    [delete_users] => 1
    [create_users] => 1
    [unfiltered_upload] => 1
    [edit_dashboard] => 1
    [update_plugins] => 1
    [delete_plugins] => 1
    [install_plugins] => 1
    [update_themes] => 1
    [install_themes] => 1
    [update_core] => 1
    [list_users] => 1
    [remove_users] => 1
    [promote_users] => 1
    [edit_theme_options] => 1
    [delete_themes] => 1
    [export] => 1
    [frm_view_forms] => 1
    [frm_edit_forms] => 1
    [frm_delete_forms] => 1
    [frm_change_settings] => 1
    [frm_view_entries] => 1
    [frm_delete_entries] => 1
    [email_users_notify] => 1
    [email_single_user] => 1
    [email_multiple_users] => 1
    [email_user_groups] => 1
    [administrator] => 1
    [edit_wp-rest-api-logs] => 1
    [delete_wp-rest-api-logs] => 1
    [read_wp-rest-api-log] => 1
    [edit_wp-rest-api-log] => 1
    [delete_wp-rest-api-log] => 1

In summary, I’m not sure what the call to check_read_permission in class-wp-rest-posts-controller.php is intended to accomplish, but the meta capability of “read” (mapped from “read_post”) is never present in the user’s capabilities in my testing, failing every request to read a draft post via the REST API. “read” is also never present when accessing this draft post from the UI, but processing continues as expected.

Change History (8)

#1 follow-up: @jnylen0
8 years ago

I've tested this using a few different test users with roles administrator, author, and contributor. All permissions to read my own or another user's post behave as I would expect in both the API and in wp-admin.

Are you sure you're logged in as the same user account in both wp-admin and the REST API? I'd suggest visiting your user profile in wp-admin and doing a request for /users/me?context=edit, and comparing the results.

There could also be a misbehaving plugin causing this issue. Have you tried from a fresh install of WP?

For future reference, it's really helpful to provide links to places in the WP code, rather than just files and line numbers. Line numbers will change over time, making the ticket information invalid, and it is much easier to follow the flow of a ticket if I don't have to stop and look up lots of different places in the code. For example:

I think both of these checks in the API are correct... if a user is trying to read a post, then we need to perform the appropriate capabilities check.

Neither of the wp-admin capabilities checks you referenced (for example https://core.trac.wordpress.org/browser/tags/4.7.2/src/wp-admin/includes/post.php?marks=1309#L1289) appear to be relevant to the core flow of editing a post.

#2 follow-up: @SergeyBiryukov
8 years ago

  • Keywords reporter-feedback added

the meta capability of “read” (mapped from “read_post”) is never present in the user’s capabilities in my testing, failing every request to read a draft post via the REST API. “read” is also never present when accessing this draft post from the UI, but processing continues as expected.

FWIW, read is the only basic capability available to all built-in user roles, including Subscriber.

The user in your testing appears to have some custom capabilities created by plugins. Does the issue still happen on a clean install?

#3 in reply to: ↑ 1 @reldev
8 years ago

Replying to jnylen0:

I've tested this using a few different test users with roles administrator, author, and contributor. All permissions to read my own or another user's post behave as I would expect in both the API and in wp-admin.

Are you sure you're logged in as the same user account in both wp-admin and the REST API? I'd suggest visiting your user profile in wp-admin and doing a request for /users/me?context=edit, and comparing the results.

There could also be a misbehaving plugin causing this issue. Have you tried from a fresh install of WP?

For future reference, it's really helpful to provide links to places in the WP code, rather than just files and line numbers. Line numbers will change over time, making the ticket information invalid, and it is much easier to follow the flow of a ticket if I don't have to stop and look up lots of different places in the code. For example:

I think both of these checks in the API are correct... if a user is trying to read a post, then we need to perform the appropriate capabilities check.

Neither of the wp-admin capabilities checks you referenced (for example https://core.trac.wordpress.org/browser/tags/4.7.2/src/wp-admin/includes/post.php?marks=1309#L1289) appear to be relevant to the core flow of editing a post.

I'm definitely logged in to the same account (verified the ID internally). I'll test with a clean install of 4.7.2 this afternoon and post the results.

Thanks for the quick response!

#4 in reply to: ↑ 2 @reldev
8 years ago

Replying to SergeyBiryukov:

the meta capability of “read” (mapped from “read_post”) is never present in the user’s capabilities in my testing, failing every request to read a draft post via the REST API. “read” is also never present when accessing this draft post from the UI, but processing continues as expected.

FWIW, read is the only basic capability available to all built-in user roles, including Subscriber.

The user in your testing appears to have some custom capabilities created by plugins. Does the issue still happen on a clean install?

I'll test with a clean install this afternoon and update with the results. Thanks!

#5 follow-up: @rmccue
8 years ago

From a quick glance, it seems that you might not be passing the nonce with the request: https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/#cookie-authentication

If you don't pass the nonce, the request will be treated as an unauthenticated request, and will give you the behaviour you're seeing here.

#6 in reply to: ↑ 5 @reldev
8 years ago

Replying to rmccue:

From a quick glance, it seems that you might not be passing the nonce with the request: https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/#cookie-authentication

If you don't pass the nonce, the request will be treated as an unauthenticated request, and will give you the behaviour you're seeing here.

Thanks for the tip. I am using OAuth and researched it quite heavily to get it to work properly with the REST API. I definitely hit this issue at one point, but am able to both create and update a post with the same authentication routine now so the authentication appears to be working at this point. Additionally, I see the correct $user->ID being checked in has_cap for this request.

I also tested with Basic Auth and see the same behavior. I'm testing with a fresh install of 4.7.2 now to see if an old plugin has left something residual in place.

#7 @reldev
8 years ago

  • Keywords reporter-feedback removed

I tested with a fresh install of 4.7.2 and don't see this problem. I had previously deactivated all my plugins except those required for authorization thinking this would eliminate any plugin related issues. I've had a couple of different membership plugins installed in the past and I'm guessing one of these left something residual in place.

Thanks for all the help in getting this resolved!

#8 @SergeyBiryukov
8 years ago

  • Milestone Awaiting Review deleted
  • Resolution set to invalid
  • Status changed from new to closed

Thanks for the follow-up!

Note: See TracTickets for help on using tickets.