Skip to content

Conversation

@justlevine
Copy link
Collaborator

What does this implement/fix? Explain your changes.

This PR exposes the hasPassword and password fields 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_posts capability. 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

@justlevine justlevine requested a review from jasonbahl March 18, 2024 08:07
@justlevine justlevine added type: enhancement Improvements to existing functionality status: in review Awaiting review before merging or closing needs: reviewer response This needs the attention of a codeowner or maintainer object type: post Relating to the Post Object Types labels Mar 18, 2024
return false;
},
'hasPassword' => function () {
return ! empty( $this->data->post_password );

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.

@coveralls
Copy link

coveralls commented Mar 18, 2024

Coverage Status

coverage: 84.396% (+0.02%) from 84.373%
when pulling 6f4e2d0 on justlevine:feat/expose-post-passwords
into a77520d on wp-graphql:develop.

Comment on lines +601 to +603
'password' => function () {
return ! empty( $this->data->post_password ) ? $this->data->post_password : null;
},
Copy link
Collaborator

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 🤔

Copy link
Collaborator Author

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.

Copy link
Collaborator

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 );
Copy link
Collaborator

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)

Copy link
Collaborator

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?

Copy link
Collaborator Author

@justlevine justlevine Mar 27, 2024

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.

Copy link
Collaborator

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.

CleanShot 2024-03-27 at 15 55 16

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:

CleanShot 2024-03-27 at 15 57 22

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 .

Copy link
Collaborator Author

@justlevine justlevine Mar 28, 2024

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?

Copy link
Collaborator

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.

CleanShot 2024-04-01 at 11 15 15

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.

CleanShot 2024-04-01 at 11 15 39

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.

CleanShot 2024-04-01 at 11 14 34

Copy link
Collaborator Author

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

Copy link
Collaborator

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 😉

justlevine and others added 3 commits March 28, 2024 07:40
- 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)
@qlty-cloud-legacy
Copy link

Code Climate has analyzed commit 6f4e2d0 and detected 2 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 2

View more on Code Climate.

@jasonbahl jasonbahl merged commit 8a790e0 into wp-graphql:develop Apr 1, 2024
@justlevine justlevine deleted the feat/expose-post-passwords branch April 1, 2024 17:55
@jasonbahl jasonbahl mentioned this pull request Apr 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs: reviewer response This needs the attention of a codeowner or maintainer object type: post Relating to the Post Object Types status: in review Awaiting review before merging or closing type: enhancement Improvements to existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose hasPassword as a queryable field instead of just as an argument

3 participants