With Rest V2 (WP4.7) how does one restrict certain RESTFUL verbs?

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

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.

Leave a Comment