WordPressのREST API周りのfilter hook / action hook のまとめと、一部のREST APIを開放する方法


もともとセキュリティの観点から不要な通信できないように、REST APIを全面禁止にしていたのですが、最近のWordPressではREST API化が進んでいますね。
最近のプラグインは、REST API対応されていているので、REST APIの解除をしてあげる必要があったりします。

このあたりのfilterやactionの知見がなく、いろいろと調べたのでまとめておきます。

## なぜこの問題を扱うのか

![Kobito.tv9peA.png](https://qiita-image-store.s3.amazonaws.com/0/37873/14870dd5-8614-47d6-75cf-4bf6f17611a8.png "Kobito.tv9peA.png")



```
✖
問題が発生しました
Your WordPress REST API has been disabled. You will need to enable it for Redirection to continue working
1. If you are unable to get anything working then Redirection may have difficulty communicating with your server. You can try manually changing this setting:REST API: Default /wp-json/Raw /index.php?rest_route=/Proxy over Admin AJAXRelative /wp-json/Form request
2. Take a look at the plugin status. It may be able to identify and "magic fix" the problem.
3. Redirection is unable to talk to your REST API. If you have disabled it then you will need to enable it.
4. Security software may be blocking Redirection. You will need to configure this to allow REST API requests.
5. Caching software, in particular Cloudflare, can cache the wrong thing. Try clearing all your caches.
6. Please temporarily disable other plugins! This fixes so many problems.
```


[Redirection](https://ja.wordpress.org/plugins/redirection/)のプラグインをupdateしたら動かなくなってしまったんですね…
こちらを開放しつつ、今まで制限していたREST API部分にできるだけ影響がでないように修正していきたいと思います。

## 結論

* 全部404にしたければ、rest_init hook に適当な処理を追加する
* REST APIのリクエストに対して個別で許可・非許可を指定するなら rest_pre_dispatch を使う


## ゴール
* WordPressにおいて、REST API周りの動作を理解する
    * 関連するfilter / actionを通理解する

## 環境
* WordPress 4.9.7
* REST API 2.0

なお、REST APIのversionの確認方法がわからず、rest-api.phpのヘッダーに記述されているものとしています。（マイナーversionとかあるのかな）。

## REST APIのサンプル

特に制限をかけないと、下記のような通信が可能です。

```
https://{site_url}/wp-json/wp/v2/posts

例：
http://127.0.0.1:9001/wp-json/wp/v2/posts
```

## REST APIを巡るコード

### 既存コード

REST APIを完全に利用できなくするための設定は下記です。
（ちょっと古いコードなので、現在推奨されていない記述もあります。）

rest_api_initに対する部分だけ、関数として切り出されていないあたり、歴史を感じました。

```php
// REST API のリクエストを停止させる設定諸々
remove_action( 'xmlrpc_rsd_apis',   'rest_output_rsd' );
remove_action( 'template_redirect', 'rest_output_link_header', 11 );
add_filter( 'rest_enabled',  '__return_false' );
add_action( 'rest_api_init', function () {
    global $wp_query;

    $template = ! empty( get_404_template() ) ? get_404_template() : get_index_template();

    $wp_query->set_404();
    status_header( 404 );
    nocache_headers();

    require_once( $template );

    exit;
}, -1 );
```

###  action hook : xmlrpc_rsd_apis

RSDは、自分で公開しているブログなどのサービスを外部から発見できるようにするための、規約です。
HTMLの中に埋め込んであげて、外部サービスがAPIを探しやすくする仕組み、という理解をしています。

APIを使わない場合は、公開規約を表示する機能をoffにしてやります。


```php:functions.php
remove_action( 'xmlrpc_rsd_apis',   'rest_output_rsd' );
```

```rest_output_rsd``` は、RSDの形式を表示する関数です。

```php:rest-api.php

/**
 * Adds the REST API URL to the WP RSD endpoint.
 *
 * @since 4.4.0
 *
 * @see get_rest_url()
 */
function rest_output_rsd() {
 $api_root = get_rest_url();

 if ( empty( $api_root ) ) {
  return;
 }
 echo $api_root;
 ?>

 <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
 <?php
}
```

### action hook : wp_head / 関数 : rest_output_link_wp_head

ヘッダーに挿入するRESTのリンクを削除するコードです。

```php:functions.php
remove_action( 'wp_head',           'rest_output_link_wp_head', 10 );
```

rest_output_link_wp_headの中身は下記のようになっています。

```php:rest-api.php
/**
 * Outputs the REST API link tag into page header.
 *
 * @since 4.4.0
 *
 * @see get_rest_url()
 */
function rest_output_link_wp_head() {
 $api_root = get_rest_url();

 if ( empty( $api_root ) ) {
  return;
 }

 echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
}
```

### action hook : rest_output_link_header / 関数: rest_output_link_header

こちらもREST API用のLinkを表示しないようにしています。
template_redirectは、ページ表示前になにかしら処理を行うときに利用するactionです。

```php:functions.php
remove_action( 'template_redirect', 'rest_output_link_header', 11 );
```

rest_output_link_header の 中身はこちらです。

```php:rest-api.php
/**
 * Sends a Link header for the REST API.
 *
 * @since 4.4.0
 */
function rest_output_link_header() {
 if ( headers_sent() ) {
  return;
 }

 $api_root = get_rest_url();

 if ( empty( $api_root ) ) {
  return;
 }

 header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
}
```

### rest_enabled

rest_enabledを無効化する記述です。

```php:functions.php
add_filter( 'rest_enabled', '__return_false' );
```

ただ、古い書き方なのでWordPress4.7.0以降では、「rest_authentication_errors」を使うように推奨されています。
実行するとログにも出ています。

```
<b>Notice</b>:  rest_enabled の使用はバージョン 4.7.0 から<strong>非推奨</strong>になっています ! 代わりに rest_authentication_errors を使ってください。 REST API は完全に無効化することはできなくなりました。代わりに rest_authentication_errors を使って API へのアクセスを制限できます。
```

see : https://developer.wordpress.org/reference/hooks/rest_enabled/

書き方としてはこんな感じ。

```php:functions.php
// WP Errorを返す場合
add_filter( 'rest_authentication_errors', 'disable_rest' );
function disable_rest(){
     return new WP_Error( 'disabled', array( 'status' => rest_authorization_required_code() ) );
}

```

### action hook : rest_api_init

rest_api_init は REST APIが初期化されたタイミングで行うactionです。
全部非許可（実質404）にしてあげるならばこの方法で良さそうです。

```php:functions.php
// REST APIのアクセスを404に
add_action( 'rest_api_init', function () {
    global $wp_query;

    $template = ! empty( get_404_template() ) ? get_404_template() : get_index_template();

    $wp_query->set_404();
    status_header( 404 );
    nocache_headers();

    require_once( $template );

    exit;
}, -1 );

```


## 一部のREST API通信を許可する

調べた感じだと、下記はREST API全体に対して処理がきいてしまう模様。
* rest_api_init
* rest_authentication_errors

なので、rest_pre_dispatchを利用する。

```php
    // rest_authentication_errors
    // rest_api_init
    // これらは、REST API全体に作用してしまう
    // 
    // 特定のプラグインを除外してREST APIを無効にするために、rest_pre_dispatch filterを使う
    function deny_rest_api_except_specific_plugins( $result, $wp_rest_server, $request ){
        // どんなリクエストがあるか覗きたい場合は、$requestの中身をログに出してみると良い（古典的デバッグ…）
        error_log(print_r($request, true));

        $namespaces = $request->get_route();

        // redirectionプラグインの除外
        if( strpos( $namespaces, 'redirection/' ) === 1 ){
            return $result;
        }

        // REST APIの非許可処理（非許可というか、401が変えるので認証資格不足）
        return new WP_Error('REST API が無効化されています.', array( 'status' => rest_authorization_required_code() ) );
    }
    add_filter( 'rest_pre_dispatch', 'deny_rest_api_except_specific_plugins');
```

## まとめ

いろいろなfilter / hookがあり複雑です。



## 参考文献

* [WP REST API](https://ja.wp-api.org/)
* [《WordPress》2017年末にWP REST API で取得してVue.jsで描画するまでのまとめ。](https://qiita.com/uto-usui/items/4eb21aec704b888936d0)
* [Really Simple Discovery](https://en.wikipedia.org/wiki/Really_Simple_Discovery)
* [WordPress4.7.0以降で「REST API」を無効にする方法が変わっていたので試しました](https://www.imamura.biz/blog/28059)
* [WordPress 4.4 で追加された REST API の停止やエンドポイントの変更など](https://qiita.com/kuck1u/items/c879271aa280da62c573)
* [WordPressのREST APIを無効化しつつ特定プラグインは動作するようにカスタマイズする方法](https://nelog.jp/deny-restapi-except-plugins)