20

I am aiming to restrict certain RESTUL verbs per custom post type. For example, given a Vocabulary custom post type, I would like to say:

Permission Matrix

+-------+---+----------+
|index  | X | GET      |
|show   | O | GET      |
|create | X | POST     |
|update | X | PATCH/PUT|
|delete | X | DELETE   |
+-------+---+----------+

The V2 doesn't seem to provide that level of control. I have gone through the source, and from what I can see, there aren't any hooks/filters to tap into changing permissions.

My current solution is the follows. It compromises of a class where you can load in a matrix of custom post types against permitted actions. This can then be called in the rest_prepare_vocabulary filter, destroying the response if the permissions don't line up.

Problem

I don't feel like this is a reasonable solution. It means permissions are being resolved in two spots (one, in core, as they are still applied) and in my filters.

Ideally, it would be at a configuration level, namely where the custom post types are defined.

In other words, I would prefer to pass in rules (along the lines of exclude_from_search, publicly_queryable, etc) rather than performing a post query "snip".

Current solution (works but not desirable)

Access.php

class Access
{
    function __construct($permissions) {
        $this->permissions = $permissions;
    }

    protected function hasId($request) {
        return ! is_null($request->get_param('id'));
    }

    protected function resolveType($request) {
        $method = strtoupper($request->get_method());

        if($method === 'GET' && $this->hasId($request)) {
            return 'show';
        } else if($method === 'GET') {
            return 'index';
        } else if($method === 'DELETE') {
            return 'delete';
        } else if($method === 'POST') {
            return 'create';
        } else if($method === 'PATCH') {
            return 'update';
        }
    }

    function validate($type, $request) {
        return in_array($this->resolveType($request), $this->permissions[$type]);
    }
}

functions.php

// bootstrap the permissions for this particular 
// application
// 
$access = new Access([
    'vocabulary' => ['show'],
]);

add_filter('rest_prepare_vocabulary', 'validate_permissions', 30, 3);
function validate_permissions($response, $post, $request) {
    global $access;

    // Give access->validate the type + request data 
    // and it will figure out if this is allowed
    //
    if( ! $access->validate($post->post_type, $request)) {
        $response->set_data([]);
        $response->set_status(403);
    }

    return $response;
};
  • 1
    Why did you instantiate Access in the global scope? Do you need it elsewhere? In case you answer this with yes, you might want to attach it to a filter instead. – kaiser Dec 13 '16 at 23:15
  • 3
    Fair question - The above is just a snippet, I'm using composer and PSR4 autoloading to draw class modules into a parent App class, of which the above snippet would be in - so it's not actually global global, it would be namespaced to \App and access is actually \App\Services\Access – Chris Dec 14 '16 at 0:11
  • 1
    I haven't investigated this issue myself, but did you check Trac for a ticket or created one if it doesn't exist? Sounds like a reasonable feature to have... – kraftner Jul 5 '17 at 7:31
  • 1
    I don't really understand the problem. "It means permissions are being resolved in two spots (one, in core, as they are still applied) and in my filters. Ideally, it would be at a configuration level, namely where the custom post types are defined." Can you possibly clarify what you mean here? Sorry if I'm stupid! – Jim Maguire Aug 8 '17 at 3:54
  • 2
    I'm downvoting this question. I don't understand why 18 people upvoted it. It's incomprehensible. – Jim Maguire Aug 15 '17 at 15:38
1

I have gone through the source, and from what I can see, there aren't any hooks/filters to tap into changing permissions.

My understanding is that this was an intentional design decision.

While the REST API was built to be extensible, it is not recommended to modify core endpoints in the way you are asking.

There is some limited information available in this section of the REST API handbook, but the gist of it is that as the API ages, more code (whether it be core or third party) will begin to depend on specific actions being available and providing standard responses.

Instead you should create a custom controller.

Custom post types can be given a custom controller by specifying a class name in the rest_controller_class argument to register_post_type().

An overview of how custom controllers should work can be found in the REST API handbook.

Something else to keep in mind is that if you create a custom controller which extends the abstract WP_REST_Controller class for a post type that supports revisions, a number of post type specific revision endpoints will be automatically created.

If it does not extend the WP_REST_Controller class, the register_routes() method is not called so you will have to manually register your custom routes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.