[wp-trac] [WordPress Trac] #38276: "Is thing public" API

WordPress Trac noreply at wordpress.org
Mon Oct 10 16:13:18 UTC 2016


#38276: "Is thing public" API
-----------------------------+-----------------------------
 Reporter:  jdgrimes         |      Owner:
     Type:  feature request  |     Status:  new
 Priority:  normal           |  Milestone:  Awaiting Review
Component:  Role/Capability  |    Version:  trunk
 Severity:  normal           |   Keywords:
  Focuses:                   |
-----------------------------+-----------------------------
 '''Question:''' How do you check if a non-logged-in user is allowed to
 view something in WordPress? (Posts specifically, and also in general?)

 '''Answer:''' Within WordPress there is no API for checking whether a non-
 logged-in user is allowed to view a post or other object.

 == Capabilities API

 There is the role and capabilities API, but only logged-in users will have
 a role or any capabilities. A visitor to the site who is not logged-in
 will thus not have even the `read` capability. `WP_User::has_cap( 'read'
 )`, and thus `current_user_can( 'read' )`, will return `false`, while
 `user_can()` will peremptorily return `false` if the user ID passed does
 not exist before even calling `WP_User::has_cap()`.

 ''Thus, while we can check if a logged-in/existing user can view a post
 via the capability API, we cannot use it to check whether a non-logged-in
 user can view the post.''

 == Underlying Philosophy

 The underlying philosophy that is used within WordPress is that objects
 such as posts are public by default. '''Everything is accessible to
 everyone unless specifically restricted.'''

 This is, in a word, a blacklist-style approach. The object is public,
 unless it blacklists/restricts itself in some way.

 When an object is public, the capabilities API, and the concept of
 capabilities in general, no longer applies as to whether that object is
 accessible. The object is accessible by default.

 The question becomes “is this post publicly visible?” rather than “does
 this user have the ability to view this post?"

 The capabilities API, on the other hand, operates on a whitelist
 philosophy—nobody is allowed to do anything (and thus nobody has any
 capabilities), unless specifically granted permission.

 These two different approaches make sense in different scenarios. However,
 it starts to get sticky when the two intersect. And that is exactly what
 happens with the `read` capability: we're now checking the whitelist to
 check a blacklist. And when the user isn't logged in, the capability isn't
 in the whitelist, which causes everything to get blacklisted—they don't
 have the capabilities to read any posts at all.

 == Why doesn't this explode?

 Obviously, WordPress has gotten along quite well up to this point without
 everything flying apart though. Non-logged-in users ''can'' view public
 posts—otherwise you and I wouldn't be able to see about 25% of the web
 right now. So yes, of course non-logged-in users do get to view those
 posts. They are public, they aren't restricted. But they get to view them
 despite not having the `read` capability.

 In other words, usually this doesn't cause any issues. That's why nobody
 has brought it up before (that I know of—probably they have). Why? Because
 usually posts are retrieved for display via `WP_Query`, and it bypasses
 the `read` capability. Instead, it works from the philosophy of public by
 default discussed above, and internally handles all of the logic for
 checking if the post is publicly visible or restricted from the current
 user. So, any time that you are using `WP_Query`, you will automatically
 get all of the posts, minus those that were restricted in some way
 (blacklisting, as it were).

 == Okay, so then who cares?

 Good question. After all, shouldn't we always be using `WP_Query` to
 retrieve posts?

 === People not using `WP_Query`

 Well, yes. But, what if we aren't retrieving a post? What if we have a
 plugin where we are storing some information that relates to a post in a
 separate table, and we want to display that information publicly on the
 site. But because that information references the post in a potentially
 identifying manner, we can't show it to users who can't view the post. I'd
 say that's a perfectly valid use-case.

 So how would we check if the current user can see the information for a
 particular post? I know what you were about to suggest: "Use
 `current_user_can()`!" But yeah, now you know why that won't work: that's
 a capability check, and non-logged-in users will not have any
 capabilities. So non-logged-in users wouldn't be able to view the
 information for any of the posts, even those that they can view publicly
 on the site.

 Now, you might say, "Just check if the post is public." Yes, and thanks
 for telling my how to do that. Should I use the
 `just_check_if_the_post_is_public()` function? :-) That's what this ticket
 is about.

 === People adding accessibility restrictions

 Another reason to care about this is that it means that when somebody
 wants to restrict the visibility of posts they have to hook-in multiple
 places: in the capabilities API for the `read` cap, and for the post
 retrieval logic in `WP_Query`.

 That is not the main focus of this ticket, and it may not really be
 practical to solve at all. However, I'm not sure how many developers
 realize that they need to consider both of these things.

 == So what do you propose?

 In this ticket, I'm '''not''' really suggesting that we:

 - throw out the `read` capability. (Back-compat nightmare.)
 - modify the caps API to handle non-logged-in users differently.
 (Fundamental change in an API, probably not practical.)

 I ''am'' suggesting that there is a need for a new API, to formally
 provide a means of determining whether a post (or any object) is publicly
 accessible. This API would include a function like `is_thing_accessible(
 $thing_id )` (and perhaps `is_thing_accessible_for_user( $user_id,
 $thing_id )`).

 == Is that ''really'' needed?

 Now, I know that some might suggest that this really isn't needed, because
 you can just check if the post has a public status. That is true in
 theory, but in practice things are more complex.

 First, let me reiterate that what I'm proposing isn't just for posts, it
 is for any objects, of which posts are one, comments another, users
 another.

 Secondly, using posts as an example,a plugin can add extra restrictions
 that cause posts with otherwise public stati to not be publicly accessible
 to all users. So it isn't a viable solution to just duplicate each core
 check that would normally apply to posts—others might be added by plugins.

 For capability checks I don't have to worry about that. I just check for a
 particular cap and anything that affects that cap will just hook into
 `map_meta_cap` and I never have to know. But if the user is logged out I
 can't use the capability API, and suddenly the onus is on me to know about
 every possible restriction that could ever apply to a post, in order to
 check if the post is public. I should be able to check
 `is_thing_accessible( $post_id )` and just forget it, same as I can do
 with `current_user_can( 'read_post', $post_id )`.

 We are talking about non-logged-in users here after all. If a restriction
 isn't taken into account, it doesn't just mean that some less privileged
 users can view the object, it means that everybody can.

 ----

 I [https://wordpress.slack.com/archives/core/p1476106801005182 brought
 this up] and [https://wordpress.slack.com/archives/core/p1476107282005192
 discussed it with @johnbillion] in the `#core` channel on Slack before
 creating this (admittedly long-winded) ticket.

--
Ticket URL: <https://core.trac.wordpress.org/ticket/38276>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list