-
Notifications
You must be signed in to change notification settings - Fork 466
feat: expose hasPassword and password fields on Post objects
#3073
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
feat: expose hasPassword and password fields on Post objects
#3073
Conversation
| return false; | ||
| }, | ||
| 'hasPassword' => function () { | ||
| return ! empty( $this->data->post_password ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid too many return statements within this method.
| 'password' => function () { | ||
| return ! empty( $this->data->post_password ) ? $this->data->post_password : null; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why did the capability check get removed from this field? Is it already protected by other logic in the model? Want to make sure we're not exposing passwords on posts to folks that shouldn't be able to access the password 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, it's protected top-level by Model::has_restricted_cap() and tested here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see my other comment: #3073 (comment)
| $this->assertNull( $actual['data']['post']['password'], 'Password should be null when unauthenticated' ); | ||
|
|
||
| // Test password protected post as admin. | ||
| wp_set_current_user( $this->admin ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to also test this with other roles.
The code used to check $this->post_type_object->cap->edit_others_posts before returning the password.
So I believe this means the password will be exposed to admins but not other roles like subscriber. (similar to the wp-admin, where an admin could see the password of a post, but a subscriber could not)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic of just checking the capability might not even be enough though, because I think if you don't have that capability BUT you are the author of the post itself you should be able to see the password?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What other caps do you want to test against?
This PR intentionally doesn't change the existing logic for handling password-protected posts (which should be addressed in #930 ), and the test currently checks:
- an unauthenticated user
- an authenticated user that doesn't have the correct cap (a regular subscriber)
- an authenticated user that does have the correct cap (admin)
Once we tackle #930, we may want to change the cap check to edit_post (as handled in WP_REST_Posts_Controller::can_access_password_content() versus the even more restrictive than currently frontend check by post_password_required() where everyone needs to supply a password), but that security loosening should probably be considered a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@justlevine ya I mean the password field itself.
In normal WordPress, not everyone is able to see the password that has been set for a post, even if they can see other fields of that post.
For example, if I'm an admin, I can set the password on a post and therefore also see the value of the existing password that is set for a post.
However, if I am authenticated in WordPress as an "author", I can see the "password protected post" and fields like the title, author, date, etc. . .BUT, I cannot see the "password" field.
That field is restricted only to users with the edit_others_posts capability.
Since WordPress itself does not expose the "password" value to users that do not have proper permission to see/update the password, WPGraphQL also should not "leak" the password to users that should not be able to see the password.
So, being able to resolve the value of password field is more nuanced than the Post itself. The has_restricted_cap() covers the Post node as a whole, but this field has more granular requirements. Not only do you have to be able to see the Node itself, BUT you also have to be able to see the password, which not every user will have permission to do so.
That's what this code does. . .it further restricts this field on the model to resolve only if the user has proper capabilities to see the password field.
I think the current logic is a bit naiive and should be more robust to not JUST check if the current user has edit_other_post cap, but also other conditions such as "is the current user the author of the post"
For example, an Author can actually publish their own post and set a password on it, and so the author of a post should be able to see the password for their own post:
But also an admin that can edit_others_posts should also be able to see the password field.
Regardless, at minimum we should probably add a test to test against the author role and ensure that they can get the title and date of a pw protected post they did not author, but cannot get the password .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasonbahl in b9e25bf, I added asserts against the current post author (password/content shown) and a different post author (password/content hidden), and fixed the subscriber assert (forgot to call set_current_user() 🤦). So hopefully that covers your concerns, but tbh I'm still not sure I fully understand:
For example, if I'm an admin, I can set the password on a post and therefore also see the value of the existing password that is set for a post.
However, if I am authenticated in WordPress as an "author", I can see the "password protected post" and fields like the title, author, date, etc. . .BUT, I cannot see the "password" field.
[image]
That field is restricted only to users with the edit_others_posts capability.
To clarify, on the frontend, WordPress uses post_password_required(), and requires everyone - including admin or the post author to provide a password, in order to see the post_content and password, while everything else remains visible.
On the dashboard list views/ edit screen, WP uses WP_REST_Posts_Controller::can_access_password_content(), allowing the current post author and any users with the edit_others_posts capability to open the Edit screen and view the password.
In both scenarios, other Post Object data remains visible (such as custom post meta).
So, being able to resolve the value of password field is more nuanced than the Post itself. The has_restricted_cap() covers the Post node as a whole, but this field has more granular requirements. Not only do you have to be able to see the Node itself, BUT you also have to be able to see the password, which not every user will have permission to do so.
Agreed, but to the opposite intent. Currently get_restricted_cap()'s check for post_password results in a stricter permissions model than either WP's frontend or REST API behavior, and makes the existing $field_config['capability'] check redundant.
In an ideal world (i.e. after or as part of #930 ) we would find what the happy medium should be (e.g. should post meta be hidden by default), but regardless of what is decided, none of the password-restricted fields are going to be doable for with just a simple $field_config['capability'] check.
Where are we losing each other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are we losing each other?
I think I was lost in the history of the project.
We had 2 different APIs for handling the same thing.
- The capability check on individual Model fields
- The
allowed_restricted_fields/get_restricted_cap()on the Model itself
You are indeed correct that this was already being covered by the allowed_restricted_fields / get_restricted_cap().
To verify manually I executed the following query as 3 different users (public, author, admin)
query GetPostsAndCurrentViewer {
posts {
nodes {
id
title
date
password
}
}
viewer {
name
roles {
nodes {
name
}
}
}
}Public User
As a public user, I can see that Password Protected posts exist, but I cannot see the password nor the password protected content. This is good. However, I cannot see whether the post hasPassword: true/false either, so we need to add hasPassword as an allowed restricted field. Public users should be able to identify whether a post has a password or not, but they should not be able to access the password itself.
Author
As an Author, I can see that password protected posts exist, and for a PW protected post that I am the author of, I can see both the content and the password. But for a PW protected post authored by another user, I can see that it exists, but I cannot see the content nor the password. However, just like a public user, I cannot see the value of the hasPassword field.
Admin
As an admin user I can see all the posts, their content, their passwords, etc whether I am the author of the post or not. This is as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for clarifying - and great catch with hasPassword!
Will push that fix + tests and ping you for the re-review when it's ready
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@justlevine I just pushed changes 😉
- add `hasPassword` as an allowed restricted field on the Post model. This allows public users to see the value of the `hasPassword` field, which can help determine UI elements to render (i.e. a PW entry form)
|
Code Climate has analyzed commit 6f4e2d0 and detected 2 issues on this pull request. Here's the issue category breakdown:
View more on Code Climate. |





What does this implement/fix? Explain your changes.
This PR exposes the
hasPasswordandpasswordfields on Post objects.It does not provide a mechanism to provide a password, which will be handled in a separate PR.
Does this close any currently open issues?
closes #3069
Any relevant logs, error output, GraphiQL screenshots, etc?
Any other comments?
Until #930 is addressed, password-protected posts require a WP cookie or the
edit_postscapability. You can see that ticket for workarounds.Where has this been tested?
Operating System: Ubuntu 20.04 (Wsl2 + devilbox + php 8.1.19)
WordPress Version: 6.4.3